<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://www.alvarovf.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.alvarovf.com/" rel="alternate" type="text/html" hreflang="es" /><updated>2024-04-14T10:26:38+00:00</updated><id>https://www.alvarovf.com/feed.xml</id><title type="html">ASIr se hace</title><subtitle>Blog de un informático</subtitle><author><name>Álvaro Vaca Ferreras</name></author><entry><title type="html">Alta disponibilidad con sistemas de ficheros distribuidos</title><link href="https://www.alvarovf.com/seguridad/2021/06/13/alta-disponibilidad-sistemas-ficheros-distribuidos.html" rel="alternate" type="text/html" title="Alta disponibilidad con sistemas de ficheros distribuidos" /><published>2021-06-13T16:02:00+00:00</published><updated>2021-06-13T16:02:00+00:00</updated><id>https://www.alvarovf.com/seguridad/2021/06/13/alta-disponibilidad-sistemas-ficheros-distribuidos</id><content type="html" xml:base="https://www.alvarovf.com/seguridad/2021/06/13/alta-disponibilidad-sistemas-ficheros-distribuidos.html"><![CDATA[<p>Esta entrada corresponde al desarrollo del <strong>T</strong>rabajo de <strong>F</strong>in de <strong>G</strong>rado (<strong>TFG</strong>) del ciclo <strong>A</strong>dministración de <strong>S</strong>istemas <strong>I</strong>nformáticos en <strong>R</strong>ed (<strong>ASIR</strong>) cursado durante la promoción 2019-2021 en el IES Gonzalo Nazareno, Dos Hermanas, Sevilla.</p>

<p>La idea a desarrollar para el TFG de forma paralela a la formación en centros de trabajo consiste en profundizar en la <strong>alta disponibilidad</strong> (<em>HA</em>), así como en los <strong>sistemas de ficheros distribuidos</strong>, necesarios para el correcto funcionamiento de por ejemplo, una base de datos replicada.</p>

<p>Dada la velocidad con la que se ha tratado el tema de la alta disponibilidad en la asignatura <strong>Seguridad y Alta Disponibilidad</strong> durante el curso, añadido además mi interés sobre el tema, considero que los conocimientos adquiridos no se adaptan a los que esperaba, convirtiéndose por tanto en una ocasión perfecta para hacerlo en el TFG.</p>

<p>El objetivo principal de este artículo es el de mostrar de una forma muy similar a lo que nos podríamos encontrar tradicionalmente en un entorno real de producción cómo funciona la alta disponibilidad de una determinada aplicación desplegada en un conjunto de servidores, desde el momento de la configuración inicial de los mismos hasta el despliegue descentralizado de la aplicación, para así seguir ofreciendo el servicio en todo momento. En este caso desplegaremos un <strong><em>PrestaShop</em></strong>.</p>

<p>Para el correcto desarrollo del proyecto, he montado un escenario en <strong>OpenStack</strong> constituido por un total de <strong>10 máquinas</strong> con sistema operativo <strong>Debian 10.6</strong>, tal y como se puede apreciar a continuación:</p>

<p><img src="https://i.ibb.co/SQTnnmg/escenario.jpg" alt="escenario" title="Escenario" /></p>

<p>Sin embargo, antes de comenzar a desarrollar la tarea de forma práctica, es necesario conocer una serie de conceptos que nos ayudarán a entender mucho mejor qué es lo que pretendemos conseguir finalmente.</p>

<p>La <strong>Alta Disponibilidad</strong> (<em>HA</em>) consiste en tener un sistema redundante que permita a uno o varios servicios seguir ejecutándose con normalidad en caso de ocurrir algún tipo de fallo. Para ello, nos serviremos de clústeres, un conjunto de equipos independientes que realizan alguna tarea común en la que se comportan como un único equipo.</p>

<p>Para ello se pretende eliminar todos los puntos únicos de fallo (<em>SPOF</em>) mediante redundancia a todos los niveles: hardware, almacenamiento, redes… y debe ser lo suficientemente inteligente como para detectar fallos, reiniciar la aplicación en otro nodo y mantener el servicio activo sin ningún tipo de interacción por parte del usuario, garantizando en todo momento su integridad.</p>

<p>Los clústeres de alta disponibilidad han ido perdiendo importancia en los últimos años dada la aparición de nuevas tecnologías como <strong>Kubernetes</strong>, que nos permiten hacerlo de una forma más abstracta y sencilla, aunque tradicionalmente se han configurado como vamos a mostrar a continuación.</p>

<p>Existen otros tipos de clústeres, aunque de menor importancia en este caso:</p>

<ul>
  <li>
    <p><strong>Alto rendimiento</strong> (<em>HPC</em>): Aumentamos la potencia computacional, por ejemplo, consiguiendo hacer un mayor número de cálculos en un menor tiempo. Se suelen utilizar equipos físicos. Utilizados en centros de investigación, ingeniería y otras actividades.</p>
  </li>
  <li>
    <p><strong>Balanceo de carga</strong>: Evitamos sobrecargar un equipo, repartiendo el tráfico entre las máquinas utilizando un algoritmo: aleatorio, <em>Round Robin</em>, carga de nodos, tiempo de respuesta… Se puede implementar un clúster de balanceo de carga sin alta disponibilidad, aunque no es lo común.</p>
  </li>
</ul>

<p>Originalmente, los clústeres surgieron como una alternativa al escalado vertical de las máquinas, pues para aumentar la potencia computacional añadimos otro nodo en paralelo que trabajará de forma síncrona con el anterior, en lugar de sustituir el equipo por uno nuevo más potente.</p>

<p>Gracias al uso de tecnologías de virtualización y <em>cloud computing</em>, dicho escalado horizontal se puede llevar a cabo con máquinas virtuales o instancias de <em>cloud</em>, en lugar de máquinas físicas, con las correspondientes ventajas que ello supone: versatilidad, evitar nuevo cableado, evitar nuevas instalaciones… aunque es posible utilizar combinaciones de las anteriores opciones.</p>

<p>Dentro del mundo de la alta disponibilidad existen una serie de términos que encontraremos con gran frecuencia y que son necesarios conocer:</p>

<ul>
  <li>
    <p><strong>Recurso</strong>: Normalmente asociado a un servicio que queremos poner a prueba de fallos, que pertenece y es gestionado por el clúster mediante el <em>software</em> correspondiente. Por ejemplo, un servidor web.</p>
  </li>
  <li>
    <p><strong>Heartbeat</strong>: Elemento utilizado en el clúster para conocer de forma constante y mediante una comunicación generalmente dedicada y cifrada, cuáles de los nodos se encuentran activos y funcionales. Es por ello que se hace la analogía de las pulsaciones o latidos de un corazón (<em>heartbeat</em>).</p>
  </li>
  <li>
    <p><strong>Split brain</strong>: Mal funcionamiento que se produce cuando se pierde la comunicación entre los nodos y estos empiezan a tomar decisiones por su cuenta.</p>
  </li>
  <li>
    <p><strong>Quorum</strong>: Mecanismo que pretende prevenir el <em>split brain</em> basándose en una decisión compartida entre los nodos, que decidirán por votación si un determinado nodo se encuentra o no activo. Es necesario que exista un número impar de nodos, para evitar empates. Es la alternativa a delegar toda la gestión del clúster en un único equipo, pues supondría la existencia de un <em>SPOF</em>.</p>
  </li>
  <li>
    <p><strong>Stonith</strong>: También conocido como <em>Shoot The Other Node In The Head</em>. Se utiliza cuando el <em>quorum</em> ha decidido que un nodo no se encuentra activo y por tanto, se asegura de que no acceda a los datos, evitando la corrupción de los mismos.</p>
  </li>
</ul>

<p>Sin embargo, existe un problema que todavía no hemos tratado, y es que la mayoría de los sistemas de ficheros tradicionales sólo pueden montarse en un equipo de forma concurrente.</p>

<p>En muchos casos, los clústeres requieren de algún sistema de almacenamiento compartido con más propiedades, como por ejemplo los de servidores web de sitios dinámicos como WordPress, ya que necesitamos que el contenido servido sea el mismo en todos ellos. Por tanto, hacemos uso de los sistemas de ficheros distribuidos, de manera que cuando un nodo escribe, el cambio es reflejado en todos los nodos.</p>

<p>Los sistemas de ficheros distribuidos utilizan su propio protocolo para comunicar el cliente y el servidor, proporcionando almacenamiento remoto de forma transparente, pues ellos lo verán como si de almacenamiento local se tratase. Incluyen tolerancia a fallos, gran escalabilidad, control de concurrencia… Entre los más conocidos se encuentran <strong>Lustre</strong>, <strong>Google FileSystem</strong>, <strong>GlusterFS</strong>… En este caso haremos uso de <strong>Ceph</strong>.</p>

<p>Una vez comprendidos los términos indispensables, todo está listo para comenzar con la creación y configuración del escenario sobre el que trabajaremos.</p>

<p>En este caso, he llevado a cabo la creación de las máquinas de forma previa, encontrándose las mismas sin ningún tipo de configuración, la cuál procederemos a realizar inicialmente con el cliente de comandos de <strong>OpenStack</strong>. La instalación de dicho cliente puede encontrarse detallada en <a href="https://www.alvarovf.com/hlc/openstack/2020/11/15/instalacion-escenario-openstack.html">anteriores artículos</a>, por lo que podemos obviarla.</p>

<p>Una vez dentro del mismo, podremos proceder a listar las instancias existentes en nuestro proyecto para así verificar que funciona correctamente. El comando a ejecutar sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>openstack server list
+--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+
| ID                                   | Name         | Status | Networks                                     | Image              | Flavor    |
+--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+
| 30bdc36b-2fa6-4017-8ef4-2925122db4d8 | ceph-dns     | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.17, 172.22.201.131 | Debian Buster 10.6 | m1.mini   |
| 468703b5-091d-4250-af14-ba40b6d1379b | ceph-client2 | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.19, 172.22.201.128 | Debian Buster 10.6 | m1.medium |
| 6f47afc2-77ad-41a1-ab73-dc94f9141663 | ceph-client1 | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.9, 172.22.201.127  | Debian Buster 10.6 | m1.medium |
| 7924631c-b9e6-4177-b894-88c5806d8735 | ceph-mon3    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.14, 172.22.201.117 | Debian Buster 10.6 | m1.medium |
| adca60ae-b729-4e6d-88c3-e47d7eca94d0 | ceph-mon2    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.5, 172.22.201.115  | Debian Buster 10.6 | m1.medium |
| c898f19d-482c-44b7-875e-380973697236 | ceph-mon1    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.11, 172.22.201.59  | Debian Buster 10.6 | m1.medium |
| 2bae6ab5-1820-4350-9555-e8ba3971dbde | ceph-osd3    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.10, 172.22.200.184 | Debian Buster 10.6 | m1.medium |
| 5d806b64-ee19-406e-a668-6c84110bbc42 | ceph-osd2    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.15, 172.22.200.108 | Debian Buster 10.6 | m1.medium |
| 9c14fb13-14e7-42d7-93e8-650cb2ebec1d | ceph-osd1    | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.13, 172.22.200.106 | Debian Buster 10.6 | m1.medium |
| e6e150bc-ecc7-4af6-b2cf-bba60e9ff5b4 | ceph-admin   | ACTIVE | red de alvaro.vaca<span class="o">=</span>10.0.0.3, 172.22.200.103  | Debian Buster 10.6 | m1.normal |
+--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+</code></pre></figure>

<p>Efectivamente, el cliente de <strong>OpenStack</strong> se encuentra actualmente operativo y nos ha mostrado la información referente a las 10 instancias creadas en mi proyecto, que como se puede apreciar, los sabores (<em>flavors</em>) asignados a las mismas se encuentran comprendidos entre los siguientes:</p>

<ul>
  <li><strong>m1.mini</strong>:
    <ul>
      <li><strong>vCPUs</strong>: 1</li>
      <li><strong>RAM</strong>: 512 MB</li>
    </ul>
  </li>
  <li><strong>m1.normal</strong>:
    <ul>
      <li><strong>vCPUs</strong>: 2</li>
      <li><strong>RAM</strong>: 1 GB</li>
    </ul>
  </li>
  <li><strong>m1.medium</strong>:
    <ul>
      <li><strong>vCPUs</strong>: 2</li>
      <li><strong>RAM</strong>: 2 GB</li>
    </ul>
  </li>
</ul>

<p>Es importante asignar suficientes recursos a las máquinas, ya que tendrán que ejecutar varios servicios medianamente exigentes en cuanto a prestaciones.</p>

<p>De forma adicional, he asociado <strong>3 volúmenes</strong> de una capacidad de <strong>5 GiB</strong> cada uno a las máquinas <strong>OSD</strong>, que tal y como posteriormente veremos, son las encargadas del almacenamiento en el clúster que montaremos. Vamos a verificar que dichos volúmenes han sido correctamente creados y asociados haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>openstack volume list
+--------------------------------------+---------+----------------+------+------------------------------------+
| ID                                   | Name    | Status         | Size | Attached to                        |
+--------------------------------------+---------+----------------+------+------------------------------------+
| be00866a-367e-4eea-94b9-7f979b510a88 | ceph-3  | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd3 on /dev/vdb  |
| e85306e6-77a1-44a4-96df-febd62ecd99f | ceph-2  | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd2 on /dev/vdb  |
| c8035f39-38b9-482e-9c22-3a262ed9cbf2 | ceph-1  | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd1 on /dev/vdb  |
+--------------------------------------+---------+----------------+------+------------------------------------+</code></pre></figure>

<p>De forma predeterminada, al crear una máquina en <strong>OpenStack</strong> se le asigna un grupo de seguridad con unas reglas de cortafuegos que en este caso no necesitamos, pues únicamente van a causarnos conflictos al intentar asignarles direcciones IP virtuales a algunas interfaces de red para el correcto funcionamiento de la alta disponibilidad, tal y como veremos durante el desarrollo del artículo.</p>

<p>Para solventar dicho problema, utilizaremos una instrucción iterativa que procederá a deshabilitar el grupo de seguridad en todas las máquinas existentes en el proyecto, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span><span class="k">for </span>i <span class="k">in </span>admin osd<span class="o">{</span>1..3<span class="o">}</span> mon<span class="o">{</span>1..3<span class="o">}</span> client<span class="o">{</span>1..2<span class="o">}</span> dns
<span class="o">&gt;</span> <span class="k">do</span>
<span class="o">&gt;</span> openstack server remove security group ceph-<span class="nv">$i</span> default
<span class="o">&gt;</span> <span class="k">done</span></code></pre></figure>

<p>Esto no es todo, ya que también tenemos que deshabilitar la seguridad en los correspondientes puertos de las máquinas, pues el <em>modus operandi</em> por defecto de una máquina sin ningún grupo de seguridad asignado, es el de bloquear la conexión en todos los puertos asociados a la misma.</p>

<p>Una vez más, utilizaremos una instrucción iterativa que deshabilitará la seguridad en los puertos existentes en las máquinas del proyecto, obteniendo en un primer paso la dirección IP de la máquina para posteriormente buscar el identificador del puerto asociado a dicha dirección. Finalmente, en una tercera instrucción, deshabilitará la seguridad en el mismo. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span><span class="k">for </span>i <span class="k">in </span>admin osd<span class="o">{</span>1..3<span class="o">}</span> mon<span class="o">{</span>1..3<span class="o">}</span> client<span class="o">{</span>1..2<span class="o">}</span> dns
<span class="o">&gt;</span> <span class="k">do</span>
<span class="o">&gt;</span> <span class="nv">ip</span><span class="o">=</span><span class="sb">`</span>openstack server list | egrep <span class="nv">$i</span> | egrep <span class="nt">-o</span> <span class="s2">"10.0.0.[0-9]{1,3}"</span><span class="sb">`</span>
<span class="o">&gt;</span> <span class="nv">port</span><span class="o">=</span><span class="sb">`</span>openstack port list | egrep <span class="nv">$ip</span> | <span class="nb">awk</span> <span class="s1">'{ print $2 }'</span><span class="sb">`</span>
<span class="o">&gt;</span> openstack port <span class="nb">set</span> <span class="nt">--disable-port-security</span> <span class="nv">$port</span>
<span class="o">&gt;</span> <span class="k">done</span></code></pre></figure>

<p>Listo, ya hemos deshabilitado la seguridad en los puertos, de manera que ya tenemos accesible todo el rango de puertos sin limitación alguna y podemos asociar direcciones IP virtuales a las interfaces de red en caso de así necesitarlo. Es importante mencionar que deshabilitar el cortafuegos es una acción un tanto precipitada, por lo que únicamente debe llevarse a cabo en un entorno controlado.</p>

<p>Ya hemos realizado todas las acciones necesarias en el cliente de <strong>OpenStack</strong>, así que llega el momento de conectarnos a las 10 instancias para seguir configurándolas una por una… o quizás no, ya que existen <strong>herramientas de orquestación</strong> como <strong>Ansible</strong> que automatizan el aprovisionamiento de <em>software</em>, la gestión de configuraciones y el despliegue de aplicaciones en servidores.</p>

<p>En otras palabras, <strong>Ansible</strong> permite a los <em>DevOps</em> gestionar sus servidores, configuraciones y aplicaciones de forma sencilla, robusta y paralela, ahorrando tiempo y esfuerzo. En caso de que la ejecución falle en uno de los servidores, se seguirá ejecutando de forma paralela sobre el resto.</p>

<p>No debe importar las veces que ejecutemos <strong>Ansible</strong> ya que el resultado de una ejecución reiterada debe ser el mismo que el de una ejecución única (idempotencia).</p>

<p>La gestión de los diferentes nodos se lleva a cabo utilizando <strong>SSH</strong> y únicamente requiere <strong>Python</strong> en el servidor remoto en el que se vaya a ejecutar para poder utilizarlo, paquete que generalmente suele venir instalado por defecto.</p>

<p>A pesar de que este proyecto no está enfocado a enseñar a utilizar <strong>Ansible</strong>, considero que ha sido una herramienta de gran utilidad y que por tanto, es necesario mencionar aquellas características indispensables para poder aplicarlo a nuestro escenario, empezando por la instalación del mismo en la máquina controladora, la cual es recomendable llevar a cabo sobre un entorno virtual Python.</p>

<p>En esta ocasión, el nombre que le voy a asignar al nuevo entorno virtual es <strong>ansible</strong>, así que lo generaré dentro de mi directorio donde almaceno todos los entornos virtuales, ejecutando para ello sobre una nueva terminal el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~<span class="nv">$ </span>python3 <span class="nt">-m</span> venv virtualenv/ansible</code></pre></figure>

<p>Una vez creado, tendremos que iniciarlo haciendo uso de <code class="language-plaintext highlighter-rouge">source</code> con el binario <strong>activate</strong> que se encuentra contenido en el directorio <strong>bin/</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~<span class="nv">$ </span><span class="nb">source </span>virtualenv/ansible/bin/activate</code></pre></figure>

<p>Una vez activado, ya podremos proceder a instalar dicha herramienta haciendo uso de <code class="language-plaintext highlighter-rouge">pip</code>, no sin antes actualizar dicho paquete en sí mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>pip <span class="nb">install</span> <span class="nt">--upgrade</span> pip</code></pre></figure>

<p>Cuando el gestor de paquetes haya sido actualizado, podremos instalar el paquete en cuestión de nombre <strong>ansible</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>pip <span class="nb">install </span>ansible</code></pre></figure>

<p>Nuestro nuevo entorno virtual se encuentra ahora equipado para hacer uso de la herramienta <strong>Ansible</strong>, sin embargo, dadas las características del proyecto que pretendemos llevar a cabo no nos será suficiente con las funcionalidades predeterminadas que incluye, de manera que necesitaremos instalar nuevas <strong>colecciones</strong> para así ampliar sus funciones.</p>

<p>En este caso, las colecciones que instalaremos son <strong>ansible.posix</strong> y <strong>community.mysql</strong>, haciendo uso de los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>ansible-galaxy collection <span class="nb">install </span>ansible.posix
<span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>ansible-galaxy collection <span class="nb">install </span>community.mysql</code></pre></figure>

<p>Todo está listo para llevar a cabo la clonación del repositorio de <strong>GitHub</strong> que contiene el proyecto <strong>Ansible</strong> que utilizaremos para configurar de forma automática nuestro escenario, de manera que en mi caso me he movido al directorio <strong>GitHub/</strong> (haciendo uso de <code class="language-plaintext highlighter-rouge">cd</code>), pues es donde almaceno todos los repositorios de <strong>GitHub</strong>, y por consecuencia, el que voy a <a href="https://github.com/alvarovaca/ansible-tfg">clonar</a>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub<span class="nv">$ </span>git clone git@github.com:alvarovaca/ansible-tfg.git
Clonando en <span class="s1">'ansible-tfg'</span>...
remote: Enumerating objects: 40, <span class="k">done</span><span class="nb">.</span>
remote: Counting objects: 100% <span class="o">(</span>40/40<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Compressing objects: 100% <span class="o">(</span>28/28<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Total 40 <span class="o">(</span>delta 0<span class="o">)</span>, reused 37 <span class="o">(</span>delta 0<span class="o">)</span>, pack-reused 0
Recibiendo objetos: 100% <span class="se">\(</span>40/40<span class="o">)</span>, 9.32 KiB | 9.32 MiB/s, listo.</code></pre></figure>

<p>Una vez completada la clonación, me he movido al directorio <strong>ansible-tfg/</strong> resultante (haciendo uso de <code class="language-plaintext highlighter-rouge">cd</code>), procediendo ahora a listar el contenido del mismo de forma recursiva y arborescente, para así poder apreciar de una manera mucho más gráfica de lo que se compone nuestro proyecto <strong>Ansible</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>tree
<span class="nb">.</span>
├── ansible.cfg
├── group_vars
│   └── all
├── hosts
├── post.yml
├── pre.yml
├── README.md
└── roles
    ├── ceph
    │   ├── files
    │   │   ├── ansible_rsa
    │   │   └── ansible_rsa.pub
    │   └── tasks
    │       └── main.yml
    ├── common
    │   ├── handlers
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    ├── dns
    │   ├── files
    │   │   ├── named.conf.local
    │   │   └── named.conf.options
    │   ├── handlers
    │   │   └── main.yml
    │   ├── tasks
    │   │   └── main.yml
    │   └── templates
    │       └── db.example.com.j2
    └── pacemaker
        ├── files
        │   └── prestashop.conf
        ├── handlers
        │   └── main.yml
        └── tasks
            └── main.yml

17 directories, 19 files</code></pre></figure>

<p>Como se puede apreciar, existen varios directorios que contienen a su vez ficheros, pero no hay de qué preocuparse, ya que los cambios a realizar sobre dicho proyecto para poder adaptarlo a cada caso son prácticamente mínimos y no requieren ningún tipo de conocimiento sobre <strong>Ansible</strong>.</p>

<p>Existen diferentes maneras de decirle a <strong>Ansible</strong> qué servidores debe gestionar. La más fácil es añadir nuestras máquinas separadas por grupos a un inventario de nombre <strong>hosts</strong>, dependiendo del ámbito al que estén dirigidas. En mi caso, el resultado final sería el siguiente (sustituir las direcciones IP por las correspondientes alcanzables):</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span><span class="nb">cat </span>hosts

<span class="o">[</span>admin]
admin1 <span class="nv">ansible_host</span><span class="o">=</span>172.22.200.103

<span class="o">[</span>osd]
osd1 <span class="nv">ansible_host</span><span class="o">=</span>172.22.200.106
osd2 <span class="nv">ansible_host</span><span class="o">=</span>172.22.200.108
osd3 <span class="nv">ansible_host</span><span class="o">=</span>172.22.200.184

<span class="o">[</span>mon]
mon1 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.59
mon2 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.115
mon3 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.117

<span class="o">[</span>client]
client1 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.127
client2 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.128

<span class="o">[</span>dns]
dns1 <span class="nv">ansible_host</span><span class="o">=</span>172.22.201.131</code></pre></figure>

<p>De forma complementaria, es recomendable generar un directorio de nombre <strong>group_vars</strong> en el que se incluyan todas las variables que apliquen a determinados grupos pertenecientes al inventario, que posteriormente podrán utilizarse dentro de plantillas o ejecución de <strong>plays</strong> (conjunto ordenado de tareas que se ejecutan).</p>

<p>En este caso, he generado un fichero de nombre <strong>all</strong> dentro del directorio previamente mencionado en el que se almacenarán las variables visibles para todos los nodos del proyecto, existiendo en este caso las siguientes:</p>

<ul>
  <li><strong>xxx_ip</strong>: Dirección IP privada de cada uno de los nodos del proyecto. Es necesario cambiarlas para adaptarlas a cada caso.</li>
  <li><strong>xxx_virt_ip</strong>: Dirección IP virtual que será asignada a cada uno de los recursos que se configurarán en alta disponibilidad. Puede modificarse pero no es necesario.</li>
  <li><strong>hacluster_crypt_pass</strong>: Contraseña encriptada para el usuario <strong>hacluster</strong>. Puede modificarse pero no es necesario.</li>
  <li><strong>hacluster_plain_pass</strong>: Contraseña en texto plano para el usuario <strong>hacluster</strong>. Puede modificarse pero no es necesario.</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span><span class="nb">cat </span>group_vars/all

admin_ip: 10.0.0.3
osd1_ip: 10.0.0.13
osd2_ip: 10.0.0.15
osd3_ip: 10.0.0.10
mon1_ip: 10.0.0.11
mon2_ip: 10.0.0.5
mon3_ip: 10.0.0.14
client1_ip: 10.0.0.9
client2_ip: 10.0.0.19
dns_ip: 10.0.0.17

apache_virt_ip: 10.0.0.200
mariadb_virt_ip: 10.0.0.201

hacluster_crypt_pass: <span class="nv">$6$vs272OD3toORiva2$SNDSrdhEDPfWo28ZoTmrC21NWVxoueRfuYbatNGprsv</span>.PY6KpOQEV/zYYsfiwGTWkCCVogEp3WSgFnA0I3b5J/
hacluster_plain_pass: hacluster</code></pre></figure>

<p>Por último, en el fichero de nombre <strong>ansible.cfg</strong> podemos incluir parámetros de configuración comunes a todo el proyecto, como por ejemplo el usuario remoto que se utilizará en las máquinas a las que vamos a conectarnos, la ruta a la clave privada para la conexión SSH…</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span><span class="nb">cat </span>ansible.cfg

<span class="o">[</span>defaults]
inventory <span class="o">=</span> hosts
remote_user <span class="o">=</span> debian
host_key_checking <span class="o">=</span> False
deprecation_warnings <span class="o">=</span> False
private_key_file <span class="o">=</span> /home/alvaro/.ssh/linux.pem
ansible_python_interpreter <span class="o">=</span> /usr/bin/python3</code></pre></figure>

<p>Los proyectos <strong>Ansible</strong> suelen organizarse en forma de <strong>roles</strong> en los que se definen una serie de tareas ordenadas a realizar (<strong>play</strong>). Por último, se genera un <strong>playbook</strong> en el que se indicará la correspondencia entre los <strong>roles</strong> y las máquinas o grupos de máquinas del inventario a los que aplicar dichas tareas.</p>

<p>En este caso, disponemos de un <strong>playbook</strong> inicial de nombre <strong>pre.yml</strong> que hace uso de un total de 3 roles previamente definidos, aplicando las siguientes tareas sobre determinados grupos de máquinas:</p>

<ul>
  <li><strong>all</strong>:
    <ul>
      <li><strong>Ensure apt does not use debian replica</strong>: Elimina cualquier repositorio de <strong>debian.org</strong> del fichero <strong>/etc/apt/sources.list</strong>.</li>
      <li><strong>Ensure apt uses cica replica</strong>: Añade los repositorios de <strong>cica.es</strong> al fichero <strong>/etc/apt/sources.list</strong> para evitar problemas con el proxy.</li>
      <li><strong>Ensure system is updated</strong>: Actualiza la paquetería instalada.</li>
      <li><strong>Set timezone to Europe/Madrid</strong>: Establece la zona horaria a Europa/Madrid.</li>
      <li><strong>Ensure hosts file is not managed by cloud_init</strong>: Evita que <em>cloud init</em> gestione el fichero <strong>/etc/hosts</strong>, haciendo que perdure.</li>
      <li><strong>Ensure hosts file does not resolve hostname</strong>: Elimina la línea referente a la resolución del <em>hostname</em> en el fichero <strong>/etc/hosts</strong>, delegando dicha tarea al servidor DNS.</li>
      <li><strong>Add DNS server to resolvconf configuration</strong>: Añade la dirección IP del servidor DNS al fichero <strong>/etc/resolvconf/resolv.conf.d/head</strong>.</li>
      <li><strong>Add search pattern to resolvconf configuration</strong>: Añade la cadena “<strong>search example.com</strong>” al fichero <strong>/etc/resolvconf/resolv.conf.d/base</strong>.</li>
      <li><strong>restart resolvconf</strong>: Reinicia el servicio <strong>resolvconf</strong>, generando así un fichero <strong>/etc/resolv.conf</strong> acorde a las configuraciones previamente realizadas.</li>
    </ul>
  </li>
  <li><strong>dns</strong>:
    <ul>
      <li><strong>Ensure bind9 is installed</strong>: Instala el paquete <strong>bind9</strong>.</li>
      <li><strong>Copy named.conf.options file</strong>: Copia el fichero <strong>named.conf.options</strong> a <strong>/etc/bind/</strong>.</li>
      <li><strong>Copy named.conf.local file</strong>: Copia el fichero <strong>named.conf.local</strong> a <strong>/etc/bind/</strong>.</li>
      <li><strong>Copy db.example.com zone using template</strong>: Utiliza una plantilla para generar un fichero de nombre <strong>/var/cache/bind/db.example.com</strong>.</li>
      <li><strong>restart bind9</strong>: Reinicia el servicio <strong>bind9</strong>, surtiendo así efecto las configuraciones previamente realizadas.</li>
    </ul>
  </li>
  <li><strong>admin, osd, mon, client</strong>:
    <ul>
      <li><strong>Create cephuser user</strong>: Crea un usuario <strong>cephuser</strong> con un directorio personal.</li>
      <li><strong>Allow cephuser to execute sudo commands without password</strong>: Añade una línea al fichero <strong>/etc/sudoers</strong> para permitir al usuario <strong>cephuser</strong> ejecutar comandos <em>sudo</em> sin contraseña.</li>
      <li><strong>Create .ssh structure</strong>: Crea el directorio <strong>.ssh</strong> en el directorio personal del usuario <strong>cephuser</strong>.</li>
      <li><strong>Copy ansible_rsa private key</strong>: Copia una clave privada previamente generada a la ruta <strong>.ssh/id_rsa</strong>.</li>
      <li><strong>Copy ansible_rsa public key</strong>: Copia una clave pública previamente generada a la ruta <strong>.ssh/id_rsa.pub</strong>.</li>
      <li><strong>Copy authorized_keys file</strong>: Copia la clave pública previamente generada a la ruta <strong>.ssh/authorized_keys</strong>.</li>
      <li><strong>Check if known_hosts file exists</strong>: Comprueba si el fichero <strong>.ssh/known_hosts</strong> existe, para que en caso de que no, realizar la siguiente tarea.</li>
      <li><strong>Scan and add SSH keys of the machines to known_hosts file</strong>: Genera el fichero <strong>.ssh/known_hosts</strong> y escanea y añade las claves SSH del resto de máquinas al mismo.</li>
      <li><strong>Change known_hosts file owner</strong>: Establece correctamente el propietario y grupo del fichero <strong>.ssh/known_hosts</strong>.</li>
      <li><strong>Add Ceph apt key</strong>: Añade la clave apt de <em>Ceph</em>.</li>
      <li><strong>Add Ceph repository</strong>: Añade el repositorio de <em>Ceph</em>.</li>
      <li><strong>Ensure needed packages are installed</strong>: Instala la paquetería necesaria para el correcto funcionamiento.</li>
    </ul>
  </li>
</ul>

<p>Por último, procederemos a ejecutar dicho <strong>playbook</strong> para así llevar a cabo las tareas previamente mencionadas sobre las máquinas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>ansible-playbook pre.yml

PLAY <span class="o">[</span>all] <span class="k">***************************************************************************************************************************************************************************</span>

TASK <span class="o">[</span>Gathering Facts] <span class="k">***************************************************************************************************************************************************************</span>
ok: <span class="o">[</span>mon1]
ok: <span class="o">[</span>admin1]
ok: <span class="o">[</span>osd1]
ok: <span class="o">[</span>osd3]
ok: <span class="o">[</span>osd2]
ok: <span class="o">[</span>mon2]
ok: <span class="o">[</span>mon3]
ok: <span class="o">[</span>dns1]
ok: <span class="o">[</span>client2]
ok: <span class="o">[</span>client1]

...

PLAY RECAP <span class="k">***************************************************************************************************************************************************************************</span>
admin1                     : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
client1                    : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
client2                    : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
dns1                       : <span class="nv">ok</span><span class="o">=</span>16   <span class="nv">changed</span><span class="o">=</span>14   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
mon1                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
mon2                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
mon3                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
osd1                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
osd2                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
osd3                       : <span class="nv">ok</span><span class="o">=</span>23   <span class="nv">changed</span><span class="o">=</span>20   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0</code></pre></figure>

<p>Tras alrededor de 30 minutos, todas las tareas se han completado correctamente, tal y como podemos apreciar en el resumen mostrado al final de la ejecución del <strong>playbook</strong>.</p>

<p>Dado el tamaño de la salida por pantalla resultante de dicha ejecución, he decidido recortarla para no ensuciar demasiado. No obstante, es posible encontrarla <a href="https://pastebin.com/8nz4Xk9w">aquí</a> al completo.</p>

<p>Antes de continuar, vamos a producir un reinicio para así cargar en memoria la nueva versión del núcleo instalada en las máquinas, volviendo para ello a nuestra terminal con el entorno virtual del cliente <strong>OpenStack</strong> y ejecutando la siguiente instrucción iterativa, que reiniciará todas las máquinas existentes:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span><span class="k">for </span>i <span class="k">in </span>admin osd<span class="o">{</span>1..3<span class="o">}</span> mon<span class="o">{</span>1..3<span class="o">}</span> client<span class="o">{</span>1..2<span class="o">}</span> dns
<span class="o">&gt;</span> <span class="k">do</span>
<span class="o">&gt;</span> openstack server reboot ceph-<span class="nv">$i</span>
<span class="o">&gt;</span> <span class="k">done</span></code></pre></figure>

<p>Una vez ejecutada la instrucción y mientras esperamos a que todas las máquinas terminen de arrancar, vamos a realizar un par de gestiones necesarias para el posterior desarrollo del artículo. La primera de ellas consiste en listar las redes existentes en nuestro proyecto, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>openstack network list
+--------------------------------------+--------------------+----------------------------------------------------------------------------+
| ID                                   | Name               | Subnets                                                                    |
+--------------------------------------+--------------------+----------------------------------------------------------------------------+
| 2526bd3a-3c01-40b3-b37d-605d99adb980 | red de alvaro.vaca | 747952ea-a0a0-4bd5-8ad6-8dec0666a299                                       |
| 49812d85-8e7a-4c31-baa2-d427692f6568 | ext-net            | 158bbe3e-3c98-485e-8042-ba6402111ea6, 6218710b-aa05-46f7-b198-7639efe3da95 |
+--------------------------------------+--------------------+----------------------------------------------------------------------------+</code></pre></figure>

<p>Como se puede apreciar, en mi proyecto existen un total de <strong>2 redes</strong>, una interna y una externa, sin embargo, la primera de ellas contiene a su vez una subred con el direccionamiento <strong>10.0.0.0/24</strong>, pudiendo observar el identificador de la misma.</p>

<p>Por último, vamos a generar una IP flotante dentro del rango enrutable de forma directa desde nuestra máquina, es decir, en la red externa, que como podemos apreciar en la salida del comando superior, tiene asociado el identificador <strong>49812d85-8e7a-4c31-baa2-d427692f6568</strong>, de manera que haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>openstack floating ip create 49812d85-8e7a-4c31-baa2-d427692f6568
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| created<span class="se">\_</span>at          | 2021-06-10T09:26:30Z                 |
| description         |                                      |
| dns<span class="se">\_</span>domain          | None                                 |
| dns<span class="se">\_</span>name            | None                                 |
| fixed<span class="se">\_</span>ip<span class="se">\_</span>address    | None                                 |
| floating<span class="se">\_</span>ip<span class="se">\_</span>address | 172.22.200.28                        |
| floating<span class="se">\_</span>network<span class="se">\_</span><span class="nb">id</span> | 49812d85-8e7a-4c31-baa2-d427692f6568 |
| <span class="nb">id</span>                  | aeffdcb1-1c4d-4471-b21e-fb606ceb30de |
| name                | 172.22.200.28                        |
| port<span class="se">\_</span>details        | None                                 |
| port<span class="se">\_</span><span class="nb">id</span>             | None                                 |
| project<span class="se">\_</span><span class="nb">id</span>          | dab5156c32654875b6c54ce23c6712a2     |
| qos<span class="se">\_</span>policy<span class="se">\_</span><span class="nb">id</span>       | None                                 |
| revision<span class="se">\_</span>number     | 1                                    |
| router<span class="se">\_</span><span class="nb">id</span>           | None                                 |
| status              | DOWN                                 |
| subnet<span class="se">\_</span><span class="nb">id</span>           | None                                 |
| tags                | <span class="se">\[</span><span class="o">]</span>                                   |
| updated<span class="se">\_</span>at          | 2021-06-10T09:26:30Z                 |
+---------------------+--------------------------------------+</code></pre></figure>

<p>La ejecución del comando ha resultado en la efectiva creación de una IP flotante, concretamente la <strong>172.22.200.28</strong>, sin embargo, vamos a verificarlo listando para ello todas las direcciones IP flotantes asociadas a nuestro proyecto, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>openstack floating ip list
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+
| ID                                   | Floating IP Address | Fixed IP Address | Port                                 | Floating Network                     | Project                          |
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+
| 0805e916-61e4-4c1c-bd82-276c2f463b6e | 172.22.201.128      | 10.0.0.19        | ad391091-7078-49c9-8032-9f06059c3534 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 360c3f8c-3677-410d-b994-992d1e1c7edd | 172.22.200.106      | 10.0.0.13        | f9d30785-3b39-439d-a906-1a33ba8b6965 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 3d54a154-9bbe-4ea3-bdf5-d447cbf9fa20 | 172.22.201.115      | 10.0.0.5         | 92a26bd0-85a3-4bec-9a23-b1bfa04b7dee | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 622afc06-92ed-4862-bfbb-531b1a0b9984 | 172.22.201.131      | 10.0.0.17        | 95eace1b-8732-416c-8c7b-f4902fb140f6 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 63fc1fc7-c73d-4eb7-905e-e017fe1daa49 | 172.22.201.127      | 10.0.0.9         | 69b86694-a9cd-47d6-bf4f-250dcee41838 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 6d48bab1-5d45-49ac-95c8-1475d2f00e79 | 172.22.201.117      | 10.0.0.14        | d308a511-871f-496d-9944-107ecdab57fb | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 6f45b92d-4130-4090-969d-f29eb761eb04 | 172.22.200.103      | 10.0.0.3         | b9db108e-c418-416a-826d-309510ddeb4f | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| 83ba8bca-f18c-4f9a-8d0d-eb710ca07c7e | 172.22.200.184      | 10.0.0.10        | c93827ca-a219-4dcd-898a-7b5065c27c63 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| aeffdcb1-1c4d-4471-b21e-fb606ceb30de | 172.22.200.28       | None             | None                                 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| f51c0df9-75ec-4d54-8af1-49f8972f49fb | 172.22.201.59       | 10.0.0.11        | 54f10e76-4470-4909-acc1-02376a3b09f2 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
| ffcfab43-9023-41bb-be96-63be788bfd51 | 172.22.200.108      | 10.0.0.15        | d967ec3f-6c62-491d-9f1a-0d6c94921c4c | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 |
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+</code></pre></figure>

<p>Como era de esperar, la IP flotante ha sido correctamente asignada a nuestro proyecto y se encuentra actualmente disponible para ser asociada a una dirección IP fija del rango de la red interna.</p>

<p>Tras ello, estableceremos una conexión SSH con la máquina <strong>ceph-admin</strong>, la cual utilizaremos para realizar la mayor parte de las gestiones restantes, haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>ssh debian@172.22.200.103 <span class="nt">-i</span> /home/alvaro/.ssh/linux.pem
Linux ceph-admin 4.19.0-16-cloud-amd64 <span class="c">#1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
</span>

The programs included with the Debian GNU/Linux system are free software<span class="p">;</span>
the exact distribution terms <span class="k">for </span>each program are described <span class="k">in </span>the
individual files <span class="k">in</span> /usr/share/doc/<span class="k">*</span>/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.</code></pre></figure>

<p>La conexión SSH se ha establecido correctamente, sin embargo, antes de proceder, considero necesario explicar qué es lo que viene a continuación.</p>

<p>Tal y como previamente hemos mencionado, necesitamos un sistema de ficheros distribuido para desplegar en alta disponibilidad cualquier aplicación que requiera compartir el almacenamiento entre los diferentes nodos sobre los que se ejecutará la misma, como es este caso.</p>

<p>La solución <em>open source</em> de la que haremos uso es conocida como <strong>Ceph</strong>. Se basa en objetos binarios y evita en todo momento las rígidas jerarquías de los sistemas convencionales. Para el almacenamiento utilizamos discos duros, como es lógico, sin embargo, un algoritmo se encarga de gestionar los objetos binarios, que están divididos en numerosas partes y repartidos entre muchos servidores de forma pseudoaleatoria (nunca almacenando más de una copia en el mismo nodo), que posteriormente vuelven a unificarse.</p>

<p>Al tener una aplicación de creciente tamaño, no sabemos cuál será la cantidad de datos a gestionar en un futuro, por tanto, el sistema de almacenamiento debe poder ampliarse fácilmente sin dejar de funcionar, mediante servidores adicionales. El cliente lo percibirá en todo momento como un sencillo directorio de un sistema de archivos tradicional, sin necesidad de que el mismo conozca absolutamente nada sobre la distribución de los mismos.</p>

<p>Otra de las características indispensables es la redundancia de los datos, ya que de nada nos sirve tener los datos distribuidos incluso en diferentes áreas geográficas del planeta si un simple fallo en uno de los discos puede causarnos la corrupción total o parcial de los datos almacenados. Por ello, el sistema de autogestión y autorrecuperación de <strong>Ceph</strong> reduce dramáticamente las interrupciones, convirtiéndolo en una excelente elección para las empresas. <strong>Facebook</strong> y <strong>Dropbox</strong> son dos de los ejemplos más influyentes.</p>

<p><img src="https://i.ibb.co/sJLdH7L/cephreplication.png" alt="replicacion" title="Replicación Ceph" /></p>

<p>Aunque en este caso no vamos a hacer uso de la siguiente característica, me parece curioso mencionar que las aplicaciones pueden acceder a <strong><em>Ceph Object Storage</em></strong> a través de una interfaz <strong>RESTful</strong> que admite las API de <strong>Amazon S3</strong> y <strong>Openstack Swift</strong>.</p>

<p>Entrando un poco más en profundidad, el sistema consta de una red de demonios que se ejecutan entre los diferentes nodos constituyentes del clúster:</p>

<ul>
  <li><strong>Monitor (MONs)</strong>: Gestionan en todo momento el estado de cada uno de los nodos en el clúster, informando de fallos lo antes posible. Se recomienda disponer de al menos tres <em>monitor nodes</em>.</li>
  <li><strong>Manager (MGRs)</strong>: Gestionan la utilización del espacio, la carga del sistema y el nivel de utilización de los nodos.</li>
  <li><strong>Object Storage Devices (OSDs)</strong>: Son los servicios que realmente gestionan los archivos: son responsables del almacenamiento, el duplicado y la restauración de los datos. Se recomienda tener al menos tres OSDs en el cluster. Se ejecuta un OSD por cada disco.</li>
  <li><strong>Metadata (MDs)</strong>: Se encargan de almacenar metadatos por motivos de rendimiento, como rutas de almacenamiento, sellos de tiempo y nombres de los archivos.</li>
</ul>

<p>El componente clave del almacenamiento de datos es el algoritmo <strong>CRUSH</strong> (<em>Controlled Replication Under Scalable Hashing</em>). Este algoritmo es capaz de encontrar un <strong>OSD</strong> con el archivo solicitado gracias a una tabla de asignaciones almacenada en los <strong>MDs</strong>.</p>

<p>Por si no era suficiente, al nivel del OSD se utiliza un <strong><em>journaling</em></strong> o registro de cambios con fecha y hora, dificultando por tanto la corrupción de los datos. Allí se guardan temporalmente los archivos que se pretenden almacenar mientras se espera a que se ubiquen correctamente en todos los OSD previstos.</p>

<p>No todo podía ser bonito, y es que como se puede apreciar, para utilizar este <em>software</em> necesitamos una red relativamente “grande” para así poder albergar de forma redundante los componentes. También se necesitan unos conocimientos suficientes, dada su complejidad. Su compatibilidad se limita a sistemas Linux, aunque estoy seguro que esto último no es un problema para nosotros.</p>

<p>Una vez comprendido de forma superficial el funcionamiento de <strong>Ceph</strong>, vamos a proceder a crear un clúster, cambiándonos para ello al usuario <strong>cephuser</strong>, pues tiene los privilegios necesarios para ejecutar comandos sin contraseña y realizar conexiones SSH al resto de máquinas. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@ceph-admin:~<span class="nv">$ </span><span class="nb">sudo </span>su - cephuser</code></pre></figure>

<p>El siguiente paso consistirá en generar un directorio en el que se almacenarán los ficheros resultantes del clúster. Su nombre no es relevante aunque es recomendable que sea significativo para evitar eliminarlo por error, de manera que en mi caso lo generaré y accederé al mismo ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~<span class="nv">$ </span><span class="nb">mkdir </span>cephcluster <span class="o">&amp;&amp;</span> <span class="nb">cd </span>cephcluster/</code></pre></figure>

<p>Para el tercero de los pasos haremos uso del binario <strong>ceph-deploy</strong> previamente instalado durante la ejecución del <strong>playbook</strong> de <strong>Ansible</strong>, que nos permitirá llevar a cabo de forma automatizada el proceso de creación del clúster. Para comenzar, tendremos que indicar el nombre de las máquinas que actuarán como <em>monitor nodes</em>. El comando final del que haré uso será:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy new ceph-mon<span class="o">{</span>1..3<span class="o">}</span></code></pre></figure>

<p>En consecuencia de la ejecución de dicho comando se habrán generado un total de 3 ficheros en el directorio actual, de manera que procederemos a verificarlo listando para ello el contenido existente mediante la ejecución del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 16
<span class="nt">-rw-r--r--</span> 1 cephuser cephuser  237 Jun  9 14:47 ceph.conf
<span class="nt">-rw-r--r--</span> 1 cephuser cephuser 5476 Jun  9 14:47 ceph-deploy-ceph.log
<span class="nt">-rw-------</span> 1 cephuser cephuser   73 Jun  9 14:47 ceph.mon.keyring</code></pre></figure>

<p>De todos los ficheros existentes únicamente nos importa el primero de ellos, de nombre <strong>ceph.conf</strong>, cuya finalidad es la de albergar parámetros de configuración del clúster, pues el segundo almacena <em>logs</em> que por ahora no son relevantes y el tercero, una clave que utilizarán los <em>monitor nodes</em> pertenecientes al clúster para autenticarse, que será distribuida a los mismos de forma totalmente automatizada.</p>

<p>Por defecto, <strong>Ceph</strong> crea un total de <strong>3 réplicas</strong> de los datos almacenados (en este caso, una en cada OSD), lo que a <em>grosso modo</em> podemos llamar un <strong>RAID-1</strong>. Como consecuencia, el tamaño total disponible y útil sería aproximadamente el de uno de los discos, ya que disponemos de 3 discos de 5 GiB, pero los otros 2 se limitarían a almacenar una copia (es decir, 1/3 del total disponible). Con dicho valor predeterminado, podrían dejar de funcionar un máximo de <strong>2 de 3 OSD</strong>, ya que la información seguiría existiendo en aquel nodo funcional.</p>

<p>Por ello, vamos a modificar dicho valor y a establecer el número de copias en 2, pudiendo aprovechar por tanto una mayor cantidad de espacio, pero sacrificando como es lógico, seguridad, pues únicamente podría morir <strong>1 de 3 OSD</strong>, haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"osd pool default size = 2"</span> <span class="o">&gt;&gt;</span> ceph.conf</code></pre></figure>

<p>Para verificar que la línea indicada se ha introducido correctamente dentro del fichero en cuestión, vamos a proceder a visualizar el contenido del mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span><span class="nb">cat </span>ceph.conf 
<span class="o">[</span>global]
fsid <span class="o">=</span> 073817c1-bd48-451e-ad7a-2fe769245057
mon_initial_members <span class="o">=</span> ceph-mon1, ceph-mon2, ceph-mon3
mon_host <span class="o">=</span> 10.0.0.11,10.0.0.5,10.0.0.14
auth_cluster_required <span class="o">=</span> cephx
auth_service_required <span class="o">=</span> cephx
auth_client_required <span class="o">=</span> cephx
osd pool default size <span class="o">=</span> 2</code></pre></figure>

<p>Efectivamente, la línea especificada ha sido correctamente introducida, además de los <em>monitor nodes</em> junto con sus correspondientes direcciones IP.</p>

<p>En un entorno más real, se utilizarían dos redes diferentes, una <strong>pública</strong> que sería indicada mediante la directiva <strong>public network</strong> que es aquella a través de la cual los clientes van a hacer uso del sistema de ficheros y una <strong>privada</strong> que sería indicada mediante la directiva <strong>private network</strong> que es aquella a través de la cual los OSD realizarán las replicaciones de datos, que debe contar con un ancho de banda considerable, para evitar cuellos de botella durante las mismas. En este caso, al únicamente existir una red, no es necesario realizar distinción.</p>

<p>El quinto paso consiste en llevar a cabo la instalación del <em>software</em> en todos los nodos pertenecientes al clúster. En este caso, la instalación la realizaremos sobre 9 de las 10 máquinas (ya que el servidor DNS no pertenece al clúster como tal), haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy <span class="nb">install </span>ceph-admin ceph-mon<span class="o">{</span>1..3<span class="o">}</span> ceph-osd<span class="o">{</span>1..3<span class="o">}</span> ceph-client<span class="o">{</span>1..2<span class="o">}</span></code></pre></figure>

<p>La instalación puede tomar unos minutos en completarse, consistiendo el siguiente paso en llevar a cabo las gestiones necesarias en los 3 <em>monitor nodes</em> previamente indicados, como por ejemplo establecer el <strong>quorum</strong>, que será el encargado de determinar por mayoría el estado de los nodos, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy mon create-initial</code></pre></figure>

<p>El séptimo paso refiere a la copia del fichero de configuración y la clave de administración en todos y cada uno de los nodos pertenecientes al clúster para así poder utilizar el intérprete de comandos de <strong>Ceph</strong> desde cualquiera de las máquinas y sin necesidad de indicar ningún parámetro adicional. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy admin ceph-admin ceph-mon<span class="o">{</span>1..3<span class="o">}</span> ceph-osd<span class="o">{</span>1..3<span class="o">}</span> ceph-client<span class="o">{</span>1..2<span class="o">}</span></code></pre></figure>

<p>Una vez finalizada la transferencia a todos los nodos, tendremos que ajustar el propietario del fichero <strong>/etc/ceph/ceph.client.admin.keyring</strong> a <strong>cephuser</strong> para que así pueda hacer uso del mismo. Comenzaremos por realizar dicha modificación en la máquina actual, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span><span class="nb">sudo chown </span>cephuser:cephuser /etc/ceph/ceph.client.admin.keyring</code></pre></figure>

<p>Tras ello, tendremos que llevar a cabo la misma acción sobre el resto de máquinas, sin embargo, podemos automatizarlo con una pequeña estructura iterativa, que quedará de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span><span class="k">for </span>i <span class="k">in </span>mon<span class="o">{</span>1..3<span class="o">}</span> osd<span class="o">{</span>1..3<span class="o">}</span> client<span class="o">{</span>1..2<span class="o">}</span>
<span class="o">&gt;</span> <span class="k">do</span>
<span class="o">&gt;</span> ssh cephuser@ceph-<span class="nv">$i</span> <span class="s2">"sudo chown cephuser:cephuser /etc/ceph/ceph.client.admin.keyring"</span>
<span class="o">&gt;</span> <span class="k">done</span></code></pre></figure>

<p>Posteriormente estableceremos los demonios de los <strong><em>manager</em></strong>, que por recomendación por parte de la documentación de <strong>Ceph</strong>, deberían ser las mismas máquinas que albergan el demonio <em>monitor</em>. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy mgr create ceph-mon<span class="o">{</span>1..3<span class="o">}</span></code></pre></figure>

<p>Una vez desplegados los demonios <em>manager</em>, es hora de configurar los <strong>OSD</strong>, así que de forma previa a ello, vamos a verificar que los 3 nodos destinados a dicha finalidad tengan correctamente asociado un volumen secundario de 5 GiB, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy disk list ceph-osd<span class="o">{</span>1..3<span class="o">}</span>
...
<span class="o">[</span>ceph-osd1][INFO  <span class="o">]</span> Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
<span class="o">[</span>ceph-osd1][INFO  <span class="o">]</span> Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors
...
<span class="o">[</span>ceph-osd2][INFO  <span class="o">]</span> Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
<span class="o">[</span>ceph-osd2][INFO  <span class="o">]</span> Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors
...
<span class="o">[</span>ceph-osd3][INFO  <span class="o">]</span> Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
<span class="o">[</span>ceph-osd3][INFO  <span class="o">]</span> Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors</code></pre></figure>

<p>Como era de esperar, en las tres máquinas existe un volumen en <strong>/dev/vdb</strong> de 5 GiB, así que procederemos a implementar la funcionalidad de OSD en dichas máquinas, utilizando para ello las rutas a los discos vacíos, en los que se crearán las particiones de datos y <em>journal</em>, haciendo para ello uso de los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy osd create <span class="nt">--data</span> /dev/vdb ceph-osd1
cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy osd create <span class="nt">--data</span> /dev/vdb ceph-osd2
cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy osd create <span class="nt">--data</span> /dev/vdb ceph-osd3</code></pre></figure>

<p>Los tres volúmenes han sido correctamente particionados y se encuentran listos para su uso, sin embargo, todavía nos falta un elemento esencial en nuestro clúster: los <strong>MDs</strong>.</p>

<p>Dado que “únicamente” contamos con 9 máquinas, tendremos que alojar dichos demonios en las mismas máquinas que ejecutan los <em>OSD</em>, aunque en un caso práctico real, sería mucho más óptimo tenerlo lo más distribuido posible. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy mds create ceph-osd<span class="o">{</span>1..3<span class="o">}</span></code></pre></figure>

<p>Una vez finalizada la ejecución del comando tendremos todos los componentes necesarios para el correcto funcionamiento del clúster activos y configurados.</p>

<p>Para hacer uso de <strong>CephFS</strong> necesitamos un mínimo de <strong>2 <em>pools</em></strong>, uno para <strong>datos</strong> y otro para <strong>metadatos</strong>. Dado el frecuente acceso a los mismos, es recomendable que se utilicen discos de baja latencia para aumentar así la eficiencia de lectura y escritura. Para la generación de dichos <em>pools</em>, ejecutaremos los siguientes comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph osd pool create cephfs_data 64
cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph osd pool create cephfs_metadata 64</code></pre></figure>

<p>Una vez finalizada la ejecución de los comandos, vamos a verificar que los <em>pools</em> se han generado correctamente en las correspondientes máquinas, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph osd lspools
1 device_health_metrics
2 cephfs_data
3 cephfs_metadata</code></pre></figure>

<p>Efectivamente, se han generado 2 nuevos <em>pools</em> con los nombres <strong>cephfs_data</strong> y <strong>cephfs_metadata</strong>, habiéndoles sido asignados los identificadores <strong>2</strong> y <strong>3</strong>, respectivamente.</p>

<p>El último paso consiste en crear un sistema de ficheros en el que podremos empezar a alojar objetos, que hará uso de los 2 <em>pools</em> creados con anterioridad, con nombre <strong>cephfs</strong>, por ejemplo. El comando a ejecutar sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph fs new cephfs cephfs_metadata cephfs_data
new fs with metadata pool 3 and data pool 2</code></pre></figure>

<p>Finalmente vamos a verificar que el sistema de ficheros se ha generado correctamente en las correspondientes máquinas, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph fs <span class="nb">ls
</span>name: cephfs, metadata pool: cephfs_metadata, data pools: <span class="o">[</span>cephfs_data <span class="o">]</span></code></pre></figure>

<p>Como era de esperar, se ha generado un nuevo sistema de ficheros de nombre <strong>cephfs</strong> que hace uso del <em>pool</em> de datos <strong>cephfs_data</strong> y del <em>pool</em> de metadatos <strong>cephfs_metadata</strong>.</p>

<p>Adicionalmente, vamos a verificar el estado general del clúster para así comprobar que todos los pasos llevados a cabo han sido efectivos y no ha ocurrido ningún problema, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph health
HEALTH_OK</code></pre></figure>

<p>Efectivamente, el estado general del clúster es correcto (<strong>HEALTH_OK</strong>), sin embargo, la información mostrada es muy limitada, de manera que vamos a profundizar un poco más haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 7m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 7m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 2 standby
    osd: 3 osds: 3 up <span class="o">(</span>since 6m<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 11m<span class="o">)</span>
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 22 objects, 2.8 KiB
    usage:   47 MiB used, 15 GiB / 15 GiB avail
    pgs:     81 active+clean</code></pre></figure>

<p>Como se puede apreciar de una forma más concreta en la salida del comando, contamos con un total de 3 <em>pools</em> y los servicios actualmente activos se encuentran alojados en las siguientes máquinas:</p>

<ul>
  <li><strong>mon</strong>: <strong>ceph-mon1</strong>, <strong>ceph-mon2</strong> y <strong>ceph-mon3</strong></li>
  <li><strong>mgr</strong>: <strong>ceph-mon1</strong>, <strong>ceph-mon2</strong> y <strong>ceph-mon3</strong></li>
  <li><strong>mds</strong>: <strong>ceph-osd1</strong>, <strong>ceph-osd2</strong> y <strong>ceph-osd3</strong></li>
  <li><strong>osd</strong>: <strong>ceph-osd1</strong>, <strong>ceph-osd2</strong> y <strong>ceph-osd3</strong></li>
</ul>

<p>Las labores en nuestro clúster <strong>Ceph</strong> han finalizado, encontrándose a partir de ahora totalmente disponible para albergar ficheros en su interior, así que es hora de explicar todo lo referente al despliegue de la aplicación sobre dicho clúster de almacenamiento distribuido, sobre el que también utilizaremos <em>software</em> para asegurar la alta disponibilidad de los servicios relacionados.</p>

<p>En este caso, vamos a utilizar <strong>Pacemaker</strong>, cuya finalidad es la de controlar y coordinar las máquinas del clúster junto a <strong>Corosync</strong>, cuya finalidad es la de permitir la comunicación entre las máquinas pertenecientes al mismo y enviar órdenes a <em>Pacemaker</em>.</p>

<p><strong>Pacemaker</strong> intenta gestionar de la mejor manera posible la distribución de los recursos, teniendo en cuenta factores como el uso de recursos de cada uno de los nodos. A pesar de ello, es posible forzar las colocaciones de los recursos en caso de así necesitarlo, por ejemplo, cuando un nodo tiene más recursos que otro y preferimos que la ejecución se lleve a cabo sobre el mismo siempre que sea posible.</p>

<p>Para interconectar las máquinas que ejecutan los servicios necesarios para el despliegue de la aplicación y tienen acceso al sistema de ficheros previamente generado, necesitaremos un usuario de nombre <strong>hacluster</strong> cuya contraseña sea la misma en ambas máquinas para así posibilitar la comunicación entre las mismas (<strong>ceph-client1</strong> y <strong>ceph-client2</strong>).</p>

<p>Vamos a desplegar un total de 4 recursos:</p>

<ul>
  <li><strong>WebVirtualIP</strong>: Dirección IP virtual (<strong>10.0.0.200</strong>) que se asignará de forma dinámica a cualquiera de los dos nodos, dependiendo de su disponibilidad.</li>
  <li><strong>WebSite</strong>: Encargado de gestionar el demonio de <strong>apache2</strong> y que irá de la mano con el recurso anterior, ya que deben estar siempre ejecutándose en la misma máquina.</li>
  <li><strong>DBVirtualIP</strong>: Dirección IP virtual (<strong>10.0.0.201</strong>) que se asignará de forma dinámica a cualquiera de los dos nodos, dependiendo de su disponibilidad.</li>
  <li><strong>Database</strong>: Encargado de gestionar el demonio de <strong>mariadb</strong> y que irá de la mano con el recurso anterior, ya que deben estar siempre ejecutándose en la misma máquina.</li>
</ul>

<p>Quizás puede llegar a ser algo confuso, así que para aclarar un poco las ideas, vamos a ir haciéndolo y entendiéndolo sobre la marcha.</p>

<p>Para ello, vamos a volver a nuestra terminal con el entorno virtual <strong>ansible</strong> y vamos a ejecutar el <strong>playbook</strong> de nombre <strong>post.yml</strong> que hace uso de un rol previamente definido, aplicando las siguientes tareas sobre el siguiente grupo de máquinas:</p>

<ul>
  <li><strong>client</strong>:
    <ul>
      <li><strong>Ensure needed packages are installed</strong>: Instala la paquetería necesaria para el correcto funcionamiento.</li>
      <li><strong>disable apache2</strong>: Para y deshabilita el servicio <strong>apache2</strong>, ya que a partir de ahora será gestionado por <em>Pacemaker</em>.</li>
      <li><strong>disable mariadb</strong>: Para y deshabilita el servicio <strong>mariadb</strong>, ya que a partir de ahora será gestionado por <em>Pacemaker</em>.</li>
      <li><strong>Obtain secret key and save as a variable</strong>: Busca la clave secreta en el fichero <strong>/etc/ceph/ceph.client.admin.keyring</strong> y la almacena en una variable.</li>
      <li><strong>Store the secret key in admin.secret file</strong>: Almacena la clave secreta en un fichero de nombre <strong>/home/cephuser/admin.secret</strong> con los permisos apropiados.</li>
      <li><strong>Mount CephFS on /ceph and add to fstab</strong>: Monta el sistema de ficheros en <strong>/ceph</strong> y añade una entrada al <strong>/etc/fstab</strong>.</li>
      <li><strong>Create the necessary structure on /ceph</strong>: Crea los subdirectorios <strong>/ceph/sql</strong> y <strong>/ceph/prestashop</strong>.</li>
      <li><strong>Check if /ceph/prestashop folder is empty before proceeding</strong>: Comprueba si el directorio <strong>/ceph/prestashop</strong> está vacío, para en ese caso, realizar las 3 siguientes tareas.</li>
      <li><strong>Download and unzip prestashop_1.7.7.0.zip package</strong>: Descarga y descomprime el paquete de PrestaShop 1.7.7.0 en el directorio <strong>/ceph/prestashop</strong>.</li>
      <li><strong>Unzip prestashop.zip package</strong>: Descomprime el paquete resultante de la previa descompresión en el mismo directorio, estableciendo correctamente los permisos y propietarios.</li>
      <li><strong>Delete unnecessary PrestaShop files</strong>: Elimina los ficheros <strong>Install_PrestaShop.html</strong> y <strong>prestashop.zip</strong> dada su nula utilidad.</li>
      <li><strong>Copy prestashop.conf VirtualHost file</strong>: Copia el fichero <strong>prestashop.conf</strong> a <strong>/etc/apache2/sites-available/</strong>.</li>
      <li><strong>Create prestashop.conf VirtualHost symlink</strong>: Crea un enlace simbólico al fichero anterior en <strong>/etc/apache2/sites-enabled/</strong>.</li>
      <li><strong>Delete 000-default.conf VirtualHost symlink</strong>: Elimina el enlace simbólico en <strong>/etc/apache2/sites-enabled/000-default.conf</strong>.</li>
      <li><strong>Create rewrite module symlink</strong>: Crea un enlace simbólico al fichero <strong>/etc/apache2/mods-available/rewrite.load</strong> en <strong>/etc/apache2/mods-enabled/</strong>.</li>
      <li><strong>Change MariaDB datadir to /ceph/sql</strong>: Modifica el fichero <strong>/etc/mysql/mariadb.conf.d/50-server.cnf</strong> y cambia el <em>datadir</em> a <strong>/ceph/sql</strong>.</li>
      <li><strong>Change MariaDB bind-address to 0.0.0.0</strong>: Modifica el fichero <strong>/etc/mysql/mariadb.conf.d/50-server.cnf</strong> y cambia la <em>bind-address</em> a <strong>0.0.0.0</strong>.</li>
      <li><strong>Check if /ceph/sql folder is empty before proceeding</strong>: Comprueba si el directorio <strong>/ceph/sql</strong> está vacío, para en ese caso, realizar la siguiente tarea.</li>
      <li><strong>Install DB prerequisites</strong>: Instala los ficheros necesarios en el directorio <strong>/ceph/sql</strong> para poder utilizarlo como <em>datadir</em>.</li>
      <li><strong>Set hacluster user password</strong>: Establece una contraseña común para el usuario <strong>hacluster</strong>.</li>
      <li><strong>Destroy default cluster</strong>: Elimina el clúster de <em>Pacemaker</em> que se genera por defecto.</li>
      <li><strong>Add hosts to the new cluster</strong>: Añade ambos nodos al nuevo clúster de <em>Pacemaker</em>.</li>
      <li><strong>Create, start and enable the new cluster</strong>: Crea, inicia y habilita el nuevo clúster de <em>Pacemaker</em> que acabamos de definir.</li>
      <li><strong>Disable STONITH property</strong>: Deshabilita la propiedad STONITH, para evitar conflictos en el <em>quorum</em>.</li>
      <li><strong>Create VirtualIP resource for Apache2</strong>: Crea el recurso de nombre <strong>WebVirtualIP</strong>.</li>
      <li><strong>Create Apache2 resource</strong>: Crea el recurso de nombre <strong>WebSite</strong>.</li>
      <li><strong>Create Apache2 and VirtualIP colocation</strong>: Crea una colocación para que los anteriores recursos se ejecuten en el mismo nodo.</li>
      <li><strong>Create VirtualIP resource for MariaDB</strong>: Crea el recurso de nombre <strong>DBVirtualIP</strong>.</li>
      <li><strong>Create MariaDB resource</strong>: Crea el recurso de nombre <strong>Database</strong>.</li>
      <li><strong>Create MariaDB and VirtualIP colocation</strong>: Crea una colocación para que los anteriores recursos se ejecuten en el mismo nodo.</li>
      <li><strong>Create constraint to preferably run all resources on the first node</strong>: Crea una restricción para que preferiblemente se ejecuten todos los recursos en el nodo <strong>ceph-client1</strong>.</li>
      <li><strong>Create a new MariaDB database</strong>: Crea una nueva base de datos de nombre <strong>prestashop</strong>.</li>
      <li><strong>Create a new user with privileges in the previous database</strong>: Crea un nuevo usuario de nombre <strong>usuario</strong> y contraseña <strong>usuario</strong> que cuenta con los privilegios suficientes sobre la base de datos previamente generada.</li>
    </ul>
  </li>
</ul>

<p>Por último, procederemos a ejecutar dicho <strong>playbook</strong> para así llevar a cabo las tareas previamente mencionadas sobre las máquinas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>ansible-playbook post.yml 

PLAY <span class="o">[</span>client] <span class="k">************************************************************************************************************************************************************************</span>

TASK <span class="o">[</span>Gathering Facts] <span class="k">***************************************************************************************************************************************************************</span>
ok: <span class="o">[</span>client1]
ok: <span class="o">[</span>client2]

...

PLAY RECAP <span class="k">***************************************************************************************************************************************************************************</span>
client1                    : <span class="nv">ok</span><span class="o">=</span>34   <span class="nv">changed</span><span class="o">=</span>31   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>0    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0   
client2                    : <span class="nv">ok</span><span class="o">=</span>13   <span class="nv">changed</span><span class="o">=</span>12   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0    <span class="nv">skipped</span><span class="o">=</span>2    <span class="nv">rescued</span><span class="o">=</span>0    <span class="nv">ignored</span><span class="o">=</span>0</code></pre></figure>

<p>Tras alrededor de 15 minutos, todas las tareas se han completado correctamente, tal y como podemos apreciar en el resumen mostrado al final de la ejecución del <strong>playbook</strong>.</p>

<p>Dado el tamaño de la salida por pantalla resultante de dicha ejecución, he decidido recortarla para no ensuciar demasiado. No obstante, es posible encontrarla <a href="https://pastebin.com/D5KUyugH">aquí</a> al completo.</p>

<p>Nuestra aplicación ya ha sido desplegada correctamente, sin embargo, existe un problema que posiblemente no habíais planteado hasta ahora. La dirección IP virtual a través de la cual accederemos al servicio web es una IP dentro de un rango interno de mi proyecto de <strong>OpenStack</strong>, lo que imposibilita su acceso de forma directa, ya que no es una red enrutable.</p>

<p>Sin embargo, existe una posibilidad un tanto “retorcida” que consiste en crear un balanceador de carga en <strong>OpenStack</strong> con una IP flotante dentro del rango alcanzable y asociarlo a dicha IP interna, de manera que a través de una especie de DNAT, conseguiríamos acceder al servidor web desde el exterior. El inconveniente es que el cliente de línea de comandos de <strong>OpenStack</strong> que hemos estado utilizando hasta ahora no nos sirve, ya que la versión que se está utilizando no soporta dicha característica, de manera que tendremos que instalar en un nuevo entorno virtual el cliente del <a href="https://docs.openstack.org/ocata/cli-reference/neutron.html">elemento</a> de <strong>OpenStack</strong> responsable de la gestión de las redes, <strong>neutron</strong>.</p>

<p>En esta ocasión, el nombre que le voy a asignar al nuevo entorno virtual es <strong>neutronclient</strong>, así que lo generaré dentro de mi directorio donde almaceno todos los entornos virtuales, ejecutando para ello sobre una nueva terminal el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~<span class="nv">$ </span>python3 <span class="nt">-m</span> venv /home/alvaro/virtualenv/neutronclient</code></pre></figure>

<p>Una vez creado, tendremos que iniciarlo haciendo uso de <code class="language-plaintext highlighter-rouge">source</code> con el binario <strong>activate</strong> que se encuentra contenido en el directorio <strong>bin/</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~<span class="nv">$ </span><span class="nb">source </span>virtualenv/neutronclient/bin/activate</code></pre></figure>

<p>Una vez activado, ya podremos proceder a instalar dicha herramienta haciendo uso de <code class="language-plaintext highlighter-rouge">pip</code>, no sin antes actualizar dicho paquete en sí mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>neutronclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>pip <span class="nb">install</span> <span class="nt">--upgrade</span> pip</code></pre></figure>

<p>Cuando el gestor de paquetes haya sido actualizado, podremos instalar el paquete en cuestión de nombre <strong>neutron</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>neutronclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>pip <span class="nb">install </span>neutron</code></pre></figure>

<p>Nuestro nuevo entorno virtual se encuentra ahora equipado para hacer uso de la herramienta <strong>neutron</strong>, así que vamos a generar un balanceador de carga asociado a la dirección IP virtual <strong>10.0.0.200</strong> y que como es lógico, pertenezca a la única subnet existente, con identificador <strong>747952ea-a0a0-4bd5-8ad6-8dec0666a299</strong>. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>neutronclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>neutron lbaas-loadbalancer-create <span class="nt">--vip-address</span> 10.0.0.200 747952ea-a0a0-4bd5-8ad6-8dec0666a299
neutron CLI is deprecated and will be removed <span class="k">in </span>the future. Use openstack CLI instead.
Created a new loadbalancer:
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| admin<span class="se">\_</span>state<span class="se">\_</span>up      | True                                 |
| description         |                                      |
| <span class="nb">id</span>                  | 155b3587-e06f-4909-b5a0-7f63d3040cd6 |
| listeners           |                                      |
| name                |                                      |
| operating<span class="se">\_</span>status    | OFFLINE                              |
| pools               |                                      |
| provider            | haproxy                              |
| provisioning<span class="se">\_</span>status | PENDING<span class="se">\_</span>CREATE                       |
| tenant<span class="se">\_</span><span class="nb">id</span>           | dab5156c32654875b6c54ce23c6712a2     |
| vip<span class="se">\_</span>address         | 10.0.0.200                           |
| vip<span class="se">\_</span>port<span class="se">\_</span><span class="nb">id</span>         | 4581b50a-f2d4-4136-880a-bb2a34ecd730 |
| vip<span class="se">\_</span>subnet<span class="se">\_</span><span class="nb">id</span>       | 747952ea-a0a0-4bd5-8ad6-8dec0666a299 |
+---------------------+--------------------------------------+</code></pre></figure>

<p>La creación del balanceador de carga se ha iniciado, sin embargo, todavía no hemos asociado la IP flotante <strong>172.22.200.28</strong> con identificador <strong>aeffdcb1-1c4d-4471-b21e-fb606ceb30de</strong> a dicho balanceador con identificador <strong>4581b50a-f2d4-4136-880a-bb2a34ecd730</strong>, así que procederemos a realizar dicha asociación ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>neutronclient<span class="o">)</span> alvaro@debian:~<span class="nv">$ </span>neutron floatingip-associate aeffdcb1-1c4d-4471-b21e-fb606ceb30de 4581b50a-f2d4-4136-880a-bb2a34ecd730
neutron CLI is deprecated and will be removed <span class="k">in </span>the future. Use openstack CLI instead.
Associated floating IP aeffdcb1-1c4d-4471-b21e-fb606ceb30de</code></pre></figure>

<p>La asociación de la IP flotante con el balanceador se ha completado correctamente, pudiendo acceder a partir de ahora desde nuestro navegador a la dirección <strong>172.22.200.28</strong> y pudiendo visualizar por tanto el contenido del servidor web alojado en la dirección <strong>10.0.0.200</strong>.</p>

<p>A pesar de que no es necesario ya que el único VirtualHost habilitado en el servidor web es el de PrestaShop, es recomendable crear una resolución estática en la máquina anfitriona para que así al acceder a <strong>www.example.com</strong>, resuelva dicha dirección a la IP flotante previamente generada. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>ansible<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span><span class="nb">sudo </span>nano /etc/hosts

127.0.0.1       localhost
127.0.1.1       debian
172.22.200.28   www.example.com

<span class="c"># The following lines are desirable for IPv6 capable hosts
</span>
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters</code></pre></figure>

<p>Ha llegado el momento de la verdad, vamos a abrir el navegador de nuestra máquina anfitriona y vamos a tratar de acceder a <strong>www.example.com</strong>, obteniendo en mi caso el siguiente resultado:</p>

<p><img src="https://i.ibb.co/hff1BXj/Captura-de-pantalla-de-2021-06-08-13-26-38.png" alt="prestashop1" title="Instalación PrestaShop" /></p>

<p>¡Genial! El hecho de que el instalador de la aplicación se esté mostrando ya es una buena señal, aunque todavía queda una fase crítica: el proceso de instalación. Dicho proceso es totalmente trivial, así que vamos a obviarlo hasta llegar al punto de introducir la información de la base de datos.</p>

<p>En mi caso, la información a introducir es la siguiente:</p>

<ul>
  <li><strong>Dirección del servidor de la base de datos</strong>: bd.example.com (también puede introducirse la dirección IP, pero ya que tenemos un registro DNS para ello, vamos a aprovecharlo)</li>
  <li><strong>Nombre de la base de datos</strong>: prestashop</li>
  <li><strong>Usuario de la base de datos</strong>: usuario</li>
  <li><strong>Contraseña de la base de datos</strong>: usuario</li>
</ul>

<p>Tras ello, presionaremos el botón de <strong>¡Comprobar la conexión con tu base de datos!</strong> para verificar que el acceso a la mismo se produce correctamente, obteniendo el siguiente resultado:</p>

<p><img src="https://i.ibb.co/8NLc95J/Captura-de-pantalla-de-2021-06-08-13-30-26.png" alt="prestashop2" title="Instalación PrestaShop" /></p>

<p>Finalmente, pulsaremos en <strong>Siguiente</strong> y la instalación de nuestro CMS comenzará, mostrándonos en todo momento una barra de progreso de la siguiente forma:</p>

<p><img src="https://i.ibb.co/kHS4vvS/Captura-de-pantalla-de-2021-06-08-13-35-52.png" alt="prestashop3" title="Instalación PrestaShop" /></p>

<p>Transcurridos unos minutos, la instalación concluirá, en mi caso, sin ningún tipo de error. Además, se nos mostrarán las credenciales de acceso que previamente hemos introducido en la segunda fase de la instalación:</p>

<p><img src="https://i.ibb.co/dkmtycb/Captura-de-pantalla-de-2021-06-08-13-46-43.png" alt="prestashop4" title="Instalación PrestaShop" /></p>

<p>Tal y como se muestra en la advertencia, por razones de seguridad es necesario eliminar el directorio <strong>install/</strong>, de manera que volveremos a nuestra terminal para proceder a ello.</p>

<p>Actualmente nos encontramos con una sesión SSH abierta a la máquina <strong>ceph-admin</strong>, sin embargo, dicha máquina no cuenta con acceso al sistema de ficheros <strong>CephFS</strong> y por tanto, no podríamos llevar a cabo ninguna modificación. La única solución consiste en abrir una segunda conexión SSH a uno de los clientes desde dicha máquina, concretamente al usuario <strong>cephuser</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ssh cephuser@ceph-client1
Linux ceph-client1 4.19.0-16-cloud-amd64 <span class="c">#1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
</span>

The programs included with the Debian GNU/Linux system are free software<span class="p">;</span>
the exact distribution terms <span class="k">for </span>each program are described <span class="k">in </span>the
individual files <span class="k">in</span> /usr/share/doc/<span class="k">*</span>/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.</code></pre></figure>

<p>Posteriormente, ya podremos llevar a cabo la eliminación de dicho directorio, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span><span class="nb">sudo rm</span> <span class="nt">-r</span> /ceph/prestashop/install/</code></pre></figure>

<p>El subdirectorio <strong>install/</strong> ha sido correctamente eliminado. De otro lado, existe una segunda práctica que es recomendable durante la instalación de un PrestaShop, consistente en modificar el nombre del directorio que contiene los ficheros de la página de administración, en este caso, del directorio <strong>admin/</strong>, para así evitar posibles ataques.</p>

<p>En este caso, voy a renombrar el subdirectorio <strong>admin/</strong> a <strong>admin966qmwvy7/</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span><span class="nb">sudo mv</span> /ceph/prestashop/admin/ /ceph/prestashop/admin966qmwvy7</code></pre></figure>

<p>Antes de volver a nuestro navegador web, vamos a listar el contenido del directorio <strong>/ceph/prestashop/</strong> para así verificar que los dos últimos comandos ejecutados han surtido efecto correctamente, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span> /ceph/prestashop/
total 557
drwxr-xr-x  9 www-data www-data     28 Dec  2  2020 admin966qmwvy7
drwxr-xr-x  5 www-data www-data      6 Dec  2  2020 app
<span class="nt">-rw-r--r--</span>  1 www-data www-data   1316 Dec  2  2020 autoload.php
drwxr-xr-x  2 www-data www-data      3 Dec  2  2020 bin
drwxr-xr-x  8 www-data www-data      9 Dec  2  2020 cache
drwxr-xr-x 25 www-data www-data    133 Dec  2  2020 classes
<span class="nt">-rw-r--r--</span>  1 www-data www-data 365681 Dec  2  2020 composer.lock
drwxr-xr-x  5 www-data www-data     16 Jun 15 11:30 config
drwxr-xr-x  4 www-data www-data      4 Dec  2  2020 controllers
drwxr-xr-x  6 www-data www-data      7 Dec  2  2020 docs
drwxr-xr-x  2 www-data www-data      2 Jun 15 11:28 download
<span class="nt">-rw-r--r--</span>  1 www-data www-data   2466 Dec  2  2020 error500.html
<span class="nt">-rw-r--r--</span>  1 www-data www-data   4830 Dec  2  2020 images.inc.php
drwxr-xr-x 19 www-data www-data     35 Jun 15 11:28 img
<span class="nt">-rw-r--r--</span>  1 www-data www-data   1169 Dec  2  2020 index.php
<span class="nt">-rw-r--r--</span>  1 www-data www-data   1256 Dec  2  2020 init.php
<span class="nt">-rw-r--r--</span>  1 www-data www-data   5072 Dec  2  2020 INSTALL.txt
drwxr-xr-x  7 www-data www-data     19 Dec  2  2020 js
<span class="nt">-rw-r--r--</span>  1 www-data www-data 186018 Dec  2  2020 LICENSES
drwxr-xr-x  3 www-data www-data     97 Dec  2  2020 localization
drwxr-xr-x  5 www-data www-data      5 Jun 15 11:36 mails
drwxr-xr-x 74 www-data www-data     74 Jun 15 11:41 modules
drwxr-xr-x  5 www-data www-data      5 Dec  2  2020 override
drwxr-xr-x  2 www-data www-data     39 Dec  2  2020 pdf
drwxr-xr-x  5 www-data www-data      4 Dec  2  2020 src
drwxr-xr-x  4 www-data www-data      8 Dec  2  2020 themes
drwxr-xr-x  3 www-data www-data      3 Dec  2  2020 tools
drwxr-xr-x  4 www-data www-data      4 Jun 15 11:28 translations
drwxr-xr-x  2 www-data www-data      2 Jun 15 11:28 upload
drwxr-xr-x  5 www-data www-data      6 Dec  2  2020 var
drwxr-xr-x 49 www-data www-data     49 Dec  2  2020 vendor
drwxr-xr-x  2 www-data www-data      2 Dec  2  2020 webservice</code></pre></figure>

<p>Como se puede apreciar, el directorio <strong>install/</strong> ha sido correctamente eliminado y el directorio <strong>admin/</strong> ha sido renombrado a <strong>admin966qmwvy7/</strong>, tal y como queríamos.</p>

<p>La instalación de nuestro CMS ha finalizado, así que vamos a añadir un artículo de prueba que nos servirá para los <em>tests</em> que aplicaremos a continuación. Para ello, accederemos a la página de administración, ubicada en <strong>www.example.com/admin966qmwvy7</strong>, que nos debería mostrar lo siguiente:</p>

<p><img src="https://i.ibb.co/gz721x1/Captura-de-pantalla-de-2021-06-09-15-41-42.png" alt="prestashop5" title="Acceso PrestaShop" /></p>

<p>Una vez solicitadas e introducidas las credenciales indicadas durante la instalación de PrestaShop, accederemos a la página de administración, dentro de la cuál accederemos al apartado <strong>Catálogo</strong> en el menú izquierdo y seguidamente pulsaremos en <strong>Productos</strong>.</p>

<p>Como es lógico, nos dirá que no tenemos ningún producto creado (a no ser que hayamos instalado los productos DEMO), así que seguidamente pulsaremos en <strong>Añade tu primer producto</strong> y rellenaremos los campos con la información que consideremos oportuna. En mi caso, el resultado final ha sido:</p>

<p><img src="https://i.ibb.co/mGCYcPJ/Captura-de-pantalla-de-2021-06-09-15-43-23.png" alt="prestashop6" title="Creación producto PrestaShop" /></p>

<p>Una vez introducida toda la información necesaria, pulsaremos en <strong>Guardar</strong> y volveremos una vez más al apartado <strong>Productos</strong> para verificar que la creación del mismo se ha completado correctamente, pudiendo apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/q91G42Q/Captura-de-pantalla-de-2021-06-09-15-43-34.png" alt="prestashop7" title="Creación producto PrestaShop" /></p>

<p>Efectivamente, mi tienda de PrestaShop ahora cuenta con un producto creado y cuya información se habrá guardado en la base de datos y en el sistema de ficheros distribuido.</p>

<p>Por último, pulsaremos en <strong>Ver mi tienda</strong> en la parte superior derecha para así visualizar la tienda junto a su nuevo producto, quedando de la siguiente forma:</p>

<p><img src="https://i.ibb.co/vHFNnnx/Captura-de-pantalla-de-2021-06-09-15-43-55.png" alt="prestashop8" title="Creación producto PrestaShop" /></p>

<p>Bien, una vez finalizada la creación del artículo en nuestra tienda, es hora de volver a la terminal para proseguir con una serie de pruebas cuya finalidad es demostrar el potencial de la alta disponibilidad, tanto por parte del clúster <strong>Ceph</strong> como del clúster <strong>Pacemaker</strong>.</p>

<p>Lo primero que haremos será visualizar el estado del clúster <strong>Ceph</strong>, para así ver como se ha incrementado el número de objetos almacenados, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 55m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 55m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 2 standby
    osd: 3 osds: 3 up <span class="o">(</span>since 53m<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 59m<span class="o">)</span>
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 38.36k objects, 723 MiB
    usage:   3.7 GiB used, 11 GiB / 15 GiB avail
    pgs:     81 active+clean</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, hay un total de más de 38000 objetos en el sistema de ficheros distribuido, suponiendo un tamaño total de más de <strong>700 MiB</strong>. Sin embargo, al existir redundancia, el espacio ocupado es superior, llegando a más de <strong>3.7 GiB</strong>.</p>

<p>Hasta ahora hemos visualizado en varias ocasiones el estado del clúster <strong>Ceph</strong> pero no hemos visto ninguna información relacionado al clúster <strong>Pacemaker</strong>, así que vamos a proceder a ello, haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span><span class="nb">sudo </span>pcs status
Cluster name: mycluster
Stack: corosync
Current DC: ceph-client1 <span class="o">(</span>version 2.0.1-9e909a5bdd<span class="o">)</span> - partition with quorum
Last updated: Tue Jun 15 11:58:02 2021
Last change: Tue Jun 15 11:25:06 2021 by root via cibadmin on ceph-client1

2 nodes configured
4 resources configured

Online: <span class="o">[</span> ceph-client1 ceph-client2 <span class="o">]</span>

Full list of resources:

 WebVirtualIP	<span class="o">(</span>ocf::heartbeat:IPaddr2<span class="o">)</span>:	Started ceph-client1
 WebSite	<span class="o">(</span>ocf::heartbeat:apache<span class="o">)</span>:	Started ceph-client1
 DBVirtualIP	<span class="o">(</span>ocf::heartbeat:IPaddr2<span class="o">)</span>:	Started ceph-client1
 Database	<span class="o">(</span>ocf::heartbeat:mysql<span class="o">)</span>:	Started ceph-client1

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled</code></pre></figure>

<p>Como era de esperar, los 2 nodos pertenecientes al clúster se encuentran actualmente activos (<strong>online</strong>) y el primero de ellos se encuentra ejecutando además los 4 recursos creados, es decir, en el momento que hemos accedido a <strong>www.example.com</strong> o <strong>bd.example.com</strong>, la máquina que ha procesado dichas peticiones ha sido <strong>ceph-client1</strong>.</p>

<p>¿Pero qué ocurriría si el primero de los nodos sufriese un fallo de <em>hardware</em> o incluso un apagón eléctrico? Vamos a comprobarlo, apagando para ello dicho nodo, ejecutando el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client1:~<span class="nv">$ </span><span class="nb">sudo </span>poweroff
Connection to ceph-client1 closed by remote host.
Connection to ceph-client1 closed.</code></pre></figure>

<p>Una vez realizada la orden de apagado de la máquina, se cerrará la sesión SSH y volveremos a la máquina <strong>ceph-admin</strong>, estableciendo ahora una conexión SSH con el segundo de los nodos, para así realizar las comprobaciones oportunas. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ssh cephuser@ceph-client2
Linux ceph-client2 4.19.0-16-cloud-amd64 <span class="c">#1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
</span>

The programs included with the Debian GNU/Linux system are free software<span class="p">;</span>
the exact distribution terms <span class="k">for </span>each program are described <span class="k">in </span>the
individual files <span class="k">in</span> /usr/share/doc/<span class="k">*</span>/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.</code></pre></figure>

<p>Acto seguido vamos a proceder a visualizar de nuevo el estado del clúster de <strong>Pacemaker</strong>, ya que dicho clúster es el afectado en caso de fallo del nodo <strong>ceph-client1</strong>, pues para el clúster <strong>Ceph</strong>, dicho nodo es un simple cliente y no supone ninguna variación en su funcionamiento. Para verificar el estado del mismo, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-client2:~<span class="nv">$ </span><span class="nb">sudo </span>pcs status
Cluster name: mycluster
Stack: corosync
Current DC: ceph-client2 <span class="o">(</span>version 2.0.1-9e909a5bdd<span class="o">)</span> - partition with quorum
Last updated: Tue Jun 15 11:59:54 2021
Last change: Tue Jun 15 11:59:43 2021 by hacluster via crmd on ceph-client2

2 nodes configured
4 resources configured

Online: <span class="o">[</span> ceph-client2 <span class="o">]</span>
OFFLINE: <span class="o">[</span> ceph-client1 <span class="o">]</span>

Full list of resources:

 WebVirtualIP	<span class="o">(</span>ocf::heartbeat:IPaddr2<span class="o">)</span>:	Started ceph-client2
 WebSite	<span class="o">(</span>ocf::heartbeat:apache<span class="o">)</span>:	Started ceph-client2
 DBVirtualIP	<span class="o">(</span>ocf::heartbeat:IPaddr2<span class="o">)</span>:	Started ceph-client2
 Database	<span class="o">(</span>ocf::heartbeat:mysql<span class="o">)</span>:	Started ceph-client2

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled</code></pre></figure>

<p>Como se puede apreciar, ahora únicamente hay un nodo activo (<strong>ceph-client2</strong>), pues el otro ha pasado a estado <strong>offline</strong> como consecuencia del apagado.</p>

<p>Además, podemos observar que los 4 recursos han sido migrados al segundo de los nodos, ya que el primero no se encuentra disponible para ello. Como consecuencia, las dos direcciones IP virtuales ahora están ubicadas en el nodo <strong>ceph-client2</strong>, así como el servidor web y el servidor de bases de datos.</p>

<p><strong>Nota</strong>: En raras ocasiones, <em>Pacemaker</em> no es capaz de levantar alguno de los recursos en el nuevo nodo tras un apagón dado que el nodo que ejecutaba dicho recurso previamente se ha quedado “colgado” y no ha terminado de morir, así que el recurso expira (<em>timeout</em>) y se para. Para solventarlo basta con ejecutar el comando <code class="language-plaintext highlighter-rouge">pcs resource cleanup [RECURSO]</code> y el recurso volverá a intentar levantarse.</p>

<p>La teoría es muy bonita, pero todavía no he visto de forma práctica si nuestra aplicación sigue funcionando tal y como debería, así que vamos a volver a <strong>www.example.com</strong> y vamos a realizar una nueva petición, por ejemplo, intentando acceder al producto previamente generado:</p>

<p><img src="https://i.ibb.co/hWKw5L8/Captura-de-pantalla-de-2021-06-09-15-48-31.png" alt="prestashop9" title="Prueba Pacemaker" /></p>

<p>Efectivamente, la aplicación sigue funcionando y atendiendo las peticiones de los clientes, sin que estos hayan notado ningún tipo de diferencia, ya que lo único que ha ocurrido es que los recursos se han movido de nodo, sin embargo, la IP desde la que se accedía a los mismos no ha variado.</p>

<p>Como es lógico, al únicamente existir dos nodos en el nodo <strong>Pacemaker</strong>, la caída del segundo de ellos sí provocaría una problema en el correcto funcionamiento de la aplicación, aunque siempre podemos aumentar el tamaño del clúster con nuevos nodos para así curarnos en salud y tener un mayor margen de error.</p>

<p>Ya hemos comprobado el correcto funcionamiento de unos de los clúster, concretamente el que opera a un mayor nivel, pero todavía nos falta por verificar el funcionamiento del clúster <strong>Ceph</strong>, para verificar si nuestro almacenamiento realmente se encuentra replicado y tiene tolerancia a fallos.</p>

<p>Para ello, vamos a salir de la máquina <strong>ceph-client2</strong> y vamos a volver a <strong>ceph-admin</strong> (comando <code class="language-plaintext highlighter-rouge">exit</code>), desde la cuál vamos a apagar el primero de los OSDs, concretamente el nodo <strong>ceph-osd1</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ssh cephuser@ceph-osd1 <span class="s2">"sudo poweroff"</span></code></pre></figure>

<p>Una vez apagado el nodo, podremos verificar el estado del clúster <strong>Ceph</strong> para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_WARN
            1 osds down
            1 host <span class="o">(</span>1 osds<span class="o">)</span> down
            Degraded data redundancy: 23026/76760 objects degraded <span class="o">(</span>29.997%<span class="o">)</span>, 49 pgs degraded, 49 pgs undersized
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 60m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 60m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 1 standby
    osd: 3 osds: 2 up <span class="o">(</span>since 112s<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 64m<span class="o">)</span>
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 38.38k objects, 722 MiB
    usage:   3.7 GiB used, 11 GiB / 15 GiB avail
    pgs:     23026/76760 objects degraded <span class="o">(</span>29.997%<span class="o">)</span>
             49 active+undersized+degraded
             32 active+clean</code></pre></figure>

<p>Como era de esperar, los <em>monitor nodes</em> han detectado el problema ocurrido en el primero de los OSD, de manera que han decidido por <em>quorum</em> marcarlo como inactivo.</p>

<p>En consecuencia a lo ocurrido, la redundancia de datos se encuentra degradada, aunque la información todavía es accesible, ya que si lo pensamos, tenemos un total de 2 copias de la información distribuidas entre 3 volúmenes, de manera que los otros 2 volúmenes todavía funcionales contienen la suficiente información como para poder acceder a los objetos almacenados en su totalidad.</p>

<p>Para verificarlo, he vuelto al navegador web y he realizado una nueva petición en <strong>www.example.com</strong>, por ejemplo, intentando acceder al gestor de módulos de PrestaShop:</p>

<p><img src="https://i.ibb.co/wwbVwBf/Captura-de-pantalla-de-2021-06-09-15-57-13.png" alt="prestashop10" title="Prueba Ceph" /></p>

<p>Efectivamente, la aplicación sigue funcionando tal y como debería, aunque un fallo en uno de los dos OSDs restantes supondría una parada de la misma. Vamos a hacer la prueba apagando para ello el segundo OSD, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ssh cephuser@ceph-osd2 <span class="s2">"sudo poweroff"</span></code></pre></figure>

<p>Una vez apagado el nodo, podremos verificar el estado del clúster <strong>Ceph</strong> para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_WARN
            1 filesystem is degraded
            insufficient standby MDS daemons available
            1 MDSs report slow metadata IOs
            2 osds down
            2 hosts <span class="o">(</span>2 osds<span class="o">)</span> down
            Reduced data availability: 25 pgs stale
            Degraded data redundancy: 38380/76760 objects degraded <span class="o">(</span>50.000%<span class="o">)</span>, 80 pgs degraded, 81 pgs undersized
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 62m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 62m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up
    osd: 3 osds: 1 up <span class="o">(</span>since 95s<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 66m<span class="o">)</span>
 
  data:
    volumes: 0/1 healthy, 1 recovering
    pools:   3 pools, 81 pgs
    objects: 38.38k objects, 722 MiB
    usage:   3.7 GiB used, 11 GiB / 15 GiB avail
    pgs:     38380/76760 objects degraded <span class="o">(</span>50.000%<span class="o">)</span>
             55 active+undersized+degraded
             25 stale+active+undersized+degraded
             1  active+undersized</code></pre></figure>

<p>Como era de esperar, los <em>monitor nodes</em> han vuelto a detectar el problema ocurrido en el segundo de los OSD, de manera que han decidido por <em>quorum</em> marcarlo como inactivo, habiendo ahora un total de 2 OSD inactivos.</p>

<p>En consecuencia a lo ocurrido, la redundancia de datos se encuentra degradada, además de ser inaccesible la información, ya que si lo pensamos, tenemos un total de 2 copias de la información distribuidas entre 3 volúmenes, de manera que el único volumen funcional no contiene una copia al completo, imposibilitando por tanto el acceso a dicha información. Si en lugar de configurar el clúster para tener 2 copias lo hubiésemos configurado para que tuviese 3, la información todavía sería accesible.</p>

<p>Antes de proceder con la última de las pruebas, quizás una de las más útiles, vamos a recuperar el estado funcional del clúster <strong>Ceph</strong>, levantando para ello las máquinas <strong>ceph-osd1</strong> y <strong>ceph-osd2</strong>. Para ello, volveremos a nuestra terminal con el cliente de comandos de <strong>OpenStack</strong> y haremos uso de los siguientes comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack server start ceph-osd1
<span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack server start ceph-osd2</code></pre></figure>

<p>Para mayor tranquilidad, vamos a verificar que el clúster ha vuelto a estar totalmente funcional ejecutando para ello el siguiente comando desde el nodo <strong>ceph-admin</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph health
HEALTH_OK</code></pre></figure>

<p>Efectivamente, el clúster vuelve a estar ahora totalmente operativo y por tanto, nuestra aplicación vuelve a estar disponible.</p>

<p>La última prueba consiste en simular un fallo en uno de los volúmenes empleados para nuestro clúster <strong>Ceph</strong>, algo que podría pasar en una situación cotidiana, y llevar a cabo un reemplazo del mismo, asegurando que la información es correctamente recuperada en el mismo.</p>

<p>Una vez más, volveremos a nuestra terminal con el cliente de comandos de <strong>OpenStack</strong> y desasociaremos en caliente el disco del nodo <strong>ceph-osd3</strong>, simulando un fallo en el disco duro de uno de los nodos como si de una situación real se tratase, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack server remove volume ceph-osd3 ceph-3</code></pre></figure>

<p>Una vez desasociado, vamos a proceder a generar un nuevo volumen de 5 GiB totalmente vacío de nombre <strong>ceph-recovery</strong>, que extrapolando al caso real, sería el nuevo disco duro que acabamos de comprar y hemos conectado a la máquina. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack volume create <span class="nt">--size</span> 5 ceph-recovery</code></pre></figure>

<p>Cuando el volumen haya sido creado, únicamente faltará asociarlo a la máquina cuyo volumen previamente asociado ha fallado, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack server add volume ceph-osd3 ceph-recovery</code></pre></figure>

<p>Supuestamente, el volumen creado ya ha sido asociado, pero para verificarlo, vamos a listar todos los volúmenes existentes y sus asociaciones, para así comprobar además que el anterior volumen también ha sido desasociado. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">(</span>openstackclient<span class="o">)</span> alvaro@debian:~/GitHub/ansible-tfg<span class="nv">$ </span>openstack volume list
+--------------------------------------+---------------+----------------+------+------------------------------------+
| ID                                   | Name          | Status         | Size | Attached to                        |
+--------------------------------------+---------------+----------------+------+------------------------------------+
| 69177591-bb69-45ca-b832-cdd9f29d48d0 | ceph-recovery | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd3 on /dev/vdb  |
| be00866a-367e-4eea-94b9-7f979b510a88 | ceph-3        | available      |    5 |                                    |
| e85306e6-77a1-44a4-96df-febd62ecd99f | ceph-2        | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd2 on /dev/vdb  |
| c8035f39-38b9-482e-9c22-3a262ed9cbf2 | ceph-1        | <span class="k">in</span><span class="nt">-use</span>         |    5 | Attached to ceph-osd1 on /dev/vdb  |
+--------------------------------------+---------------+----------------+------+------------------------------------+</code></pre></figure>

<p>Como se puede apreciar, el volumen de nombre <strong>ceph-3</strong> ha sido correctamente desasociado y se encuentra disponible para su uso, mientras que de otro lado, el volumen <strong>ceph-recovery</strong> ha sido creado y asociado a la máquina <strong>ceph-osd3</strong> sin ningún tipo de problema.</p>

<p>Una vez cambiado el volumen, podremos verificar el estado del clúster <strong>Ceph</strong> para comprobar si ha ocurrido algún tipo de modificación en el mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_WARN
            1 osds down
            1 host <span class="o">(</span>1 osds<span class="o">)</span> down
            Degraded data redundancy: 26952/76760 objects degraded <span class="o">(</span>35.112%<span class="o">)</span>, 55 pgs degraded, 56 pgs undersized
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 67m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 67m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 2 standby
    osd: 3 osds: 2 up <span class="o">(</span>since 77s<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 71m<span class="o">)</span>
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 38.38k objects, 722 MiB
    usage:   2.7 GiB used, 12 GiB / 15 GiB avail
    pgs:     26952/76760 objects degraded <span class="o">(</span>35.112%<span class="o">)</span>
             55 active+undersized+degraded
             25 active+clean
             1  active+undersized</code></pre></figure>

<p>Como era de esperar, la redundancia de datos se encuentra degradada, ya que uno de los volúmenes ha “fallado”, y por consecuencia, el OSD se marca como inactivo, ya que si recordamos la explicación inicial, el demonio del OSD se encuentra asociado al volumen, no a la máquina en sí.</p>

<p>Entrando un poco más en detalle, otra de las acciones que se han llevado a cabo en un segundo plano ha sido una redistribución gracias al algoritmo CRUSH para que así los clientes tengan una nueva ruta para encontrar los objetos, sin importar la pérdida de dicho volumen.</p>

<p>Para llevar a cabo la sustitución del volumen en el clúster, lo primero que tendremos que hacer será sacar al anterior del mismo, necesitando para ello el identificador, que podremos obtener mediante la ejecución del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="se">\$</span> ceph osd tree | <span class="nb">grep</span> <span class="nt">-i</span> down
 2    hdd  0.00490          osd.2         down   1.00000  1.00000</code></pre></figure>

<p>En este caso, el identificador del OSD fallido es <strong>osd.2</strong>, de manera que lo eliminaremos del clúster haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph osd destroy osd.2 <span class="nt">--yes-i-really-mean-it</span>
destroyed osd.2</code></pre></figure>

<p>Tal y como podemos apreciar en la salida del comando ejecutado, el OSD ha sido destruido, de manera que todo está listo para insertar el nuevo, no sin antes listar los discos asociados al nodo <strong>ceph-osd3</strong>, para así verificar que reconoce el nuevo volumen de 5 GiB. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy disk list ceph-osd3
...
<span class="o">[</span>ceph-osd3][INFO  <span class="o">]</span> Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
<span class="o">[</span>ceph-osd3][INFO  <span class="o">]</span> Disk /dev/vdc: 5 GiB, 5368709120 bytes, 10485760 sectors</code></pre></figure>

<p>Efectivamente, así ha sido. La única diferencia es que en lugar de estar ubicado en <strong>/dev/vdb</strong>, lo está en <strong>/dev/vdc</strong>, pero no tiene mayor importancia, pues bastaría con cambiar dicha letra a la hora de darle formato al disco, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph-deploy osd create <span class="nt">--data</span> /dev/vdc ceph-osd3</code></pre></figure>

<p>El volumen ha sido correctamente particionado y se debería encontrar listo para su uso, así que vamos a verificar el estado del clúster <strong>Ceph</strong> para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_WARN
            Degraded data redundancy: 31830/76760 objects degraded <span class="o">(</span>41.467%<span class="o">)</span>, 47 pgs degraded, 47 pgs undersized
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 99m<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 98m<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 2 standby
    osd: 4 osds: 3 up <span class="o">(</span>since 29m<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 22m<span class="o">)</span><span class="p">;</span> 48 remapped pgs
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 38.38k objects, 723 MiB
    usage:   1.8 GiB used, 13 GiB / 15 GiB avail
    pgs:     31830/76760 objects degraded <span class="o">(</span>41.467%<span class="o">)</span>
             1347/76760 objects misplaced <span class="o">(</span>1.755%<span class="o">)</span>
             40 active+recovery_wait+undersized+degraded+remapped
             33 active+clean
             6  active+undersized+degraded+remapped+backfill_wait
             1  active+remapped+backfill_wait
             1  active+recovering+undersized+degraded+remapped
 
  io:
    recovery: 180 KiB/s, 9 objects/s</code></pre></figure>

<p>Como se puede apreciar en la parte inferior de la salida del comando ejecutado, el clúster ha comenzado un proceso de auto-reparación, pues se estará rellenando el nuevo volumen con la parte de información correspondiente, que en este caso duró hasta 2 horas.</p>

<p>Una vez transcurrido un periodo de tiempo considerable, podremos volver a hacer uso del comando anterior para verificar si el proceso ha finalizado:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">cephuser@ceph-admin:~/cephcluster<span class="nv">$ </span>ceph status
  cluster:
    <span class="nb">id</span>:     5d47d41b-b223-4a16-bbac-688d8a61c359
    health: HEALTH_WARN
            1 daemons have recently crashed
 
  services:
    mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 <span class="o">(</span>age 4h<span class="o">)</span>
    mgr: ceph-mon1<span class="o">(</span>active, since 4h<span class="o">)</span>, standbys: ceph-mon2, ceph-mon3
    mds: 1/1 daemons up, 2 standby
    osd: 4 osds: 3 up <span class="o">(</span>since 3h<span class="o">)</span>, 3 <span class="k">in</span> <span class="o">(</span>since 3h<span class="o">)</span>
 
  data:
    volumes: 1/1 healthy
    pools:   3 pools, 81 pgs
    objects: 38.38k objects, 723 MiB
    usage:   2.7 GiB used, 12 GiB / 15 GiB avail
    pgs:     81 active+clean</code></pre></figure>

<p>Efectivamente, tras un buen rato de espera, el proceso de auto-reparación ha finalizado y el disco se encuentra ahora totalmente funcional, tal y como se encontraba antes del cambio.</p>

<p>Una vez más, vamos a hacer una prueba práctica para así demostrar que la teoría es correcta, realizando para ello una nueva petición en <strong>www.example.com</strong>, por ejemplo, intentando acceder al formulario de contacto de PrestaShop:</p>

<p><img src="https://i.ibb.co/kyGh055/Captura-de-pantalla-de-2021-06-10-09-38-17.png" alt="prestashop11" title="Prueba Ceph" /></p>

<p>Nuestra aplicación se encuentra actualmente operativa y preparada para tolerar prácticamente la mayoría de fallos tanto de <em>hardware</em> como de <em>software</em>, proporcionando una gran seguridad y margen de error.</p>

<p>Espero que este artículo haya sido de gran ayuda además de interesante para todo el mundo. Si os habéis entretenido al menos la mitad de lo que lo he hecho yo, ¡me doy por satisfecho!</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="seguridad" /><summary type="html"><![CDATA[Esta entrada corresponde al desarrollo del Trabajo de Fin de Grado (TFG) del ciclo Administración de Sistemas Informáticos en Red (ASIR) cursado durante la promoción 2019-2021 en el IES Gonzalo Nazareno, Dos Hermanas, Sevilla. La idea a desarrollar para el TFG de forma paralela a la formación en centros de trabajo consiste en profundizar en la alta disponibilidad (HA), así como en los sistemas de ficheros distribuidos, necesarios para el correcto funcionamiento de por ejemplo, una base de datos replicada. Dada la velocidad con la que se ha tratado el tema de la alta disponibilidad en la asignatura Seguridad y Alta Disponibilidad durante el curso, añadido además mi interés sobre el tema, considero que los conocimientos adquiridos no se adaptan a los que esperaba, convirtiéndose por tanto en una ocasión perfecta para hacerlo en el TFG. El objetivo principal de este artículo es el de mostrar de una forma muy similar a lo que nos podríamos encontrar tradicionalmente en un entorno real de producción cómo funciona la alta disponibilidad de una determinada aplicación desplegada en un conjunto de servidores, desde el momento de la configuración inicial de los mismos hasta el despliegue descentralizado de la aplicación, para así seguir ofreciendo el servicio en todo momento. En este caso desplegaremos un PrestaShop. Para el correcto desarrollo del proyecto, he montado un escenario en OpenStack constituido por un total de 10 máquinas con sistema operativo Debian 10.6, tal y como se puede apreciar a continuación: Sin embargo, antes de comenzar a desarrollar la tarea de forma práctica, es necesario conocer una serie de conceptos que nos ayudarán a entender mucho mejor qué es lo que pretendemos conseguir finalmente. La Alta Disponibilidad (HA) consiste en tener un sistema redundante que permita a uno o varios servicios seguir ejecutándose con normalidad en caso de ocurrir algún tipo de fallo. Para ello, nos serviremos de clústeres, un conjunto de equipos independientes que realizan alguna tarea común en la que se comportan como un único equipo. Para ello se pretende eliminar todos los puntos únicos de fallo (SPOF) mediante redundancia a todos los niveles: hardware, almacenamiento, redes… y debe ser lo suficientemente inteligente como para detectar fallos, reiniciar la aplicación en otro nodo y mantener el servicio activo sin ningún tipo de interacción por parte del usuario, garantizando en todo momento su integridad. Los clústeres de alta disponibilidad han ido perdiendo importancia en los últimos años dada la aparición de nuevas tecnologías como Kubernetes, que nos permiten hacerlo de una forma más abstracta y sencilla, aunque tradicionalmente se han configurado como vamos a mostrar a continuación. Existen otros tipos de clústeres, aunque de menor importancia en este caso: Alto rendimiento (HPC): Aumentamos la potencia computacional, por ejemplo, consiguiendo hacer un mayor número de cálculos en un menor tiempo. Se suelen utilizar equipos físicos. Utilizados en centros de investigación, ingeniería y otras actividades. Balanceo de carga: Evitamos sobrecargar un equipo, repartiendo el tráfico entre las máquinas utilizando un algoritmo: aleatorio, Round Robin, carga de nodos, tiempo de respuesta… Se puede implementar un clúster de balanceo de carga sin alta disponibilidad, aunque no es lo común. Originalmente, los clústeres surgieron como una alternativa al escalado vertical de las máquinas, pues para aumentar la potencia computacional añadimos otro nodo en paralelo que trabajará de forma síncrona con el anterior, en lugar de sustituir el equipo por uno nuevo más potente. Gracias al uso de tecnologías de virtualización y cloud computing, dicho escalado horizontal se puede llevar a cabo con máquinas virtuales o instancias de cloud, en lugar de máquinas físicas, con las correspondientes ventajas que ello supone: versatilidad, evitar nuevo cableado, evitar nuevas instalaciones… aunque es posible utilizar combinaciones de las anteriores opciones. Dentro del mundo de la alta disponibilidad existen una serie de términos que encontraremos con gran frecuencia y que son necesarios conocer: Recurso: Normalmente asociado a un servicio que queremos poner a prueba de fallos, que pertenece y es gestionado por el clúster mediante el software correspondiente. Por ejemplo, un servidor web. Heartbeat: Elemento utilizado en el clúster para conocer de forma constante y mediante una comunicación generalmente dedicada y cifrada, cuáles de los nodos se encuentran activos y funcionales. Es por ello que se hace la analogía de las pulsaciones o latidos de un corazón (heartbeat). Split brain: Mal funcionamiento que se produce cuando se pierde la comunicación entre los nodos y estos empiezan a tomar decisiones por su cuenta. Quorum: Mecanismo que pretende prevenir el split brain basándose en una decisión compartida entre los nodos, que decidirán por votación si un determinado nodo se encuentra o no activo. Es necesario que exista un número impar de nodos, para evitar empates. Es la alternativa a delegar toda la gestión del clúster en un único equipo, pues supondría la existencia de un SPOF. Stonith: También conocido como Shoot The Other Node In The Head. Se utiliza cuando el quorum ha decidido que un nodo no se encuentra activo y por tanto, se asegura de que no acceda a los datos, evitando la corrupción de los mismos. Sin embargo, existe un problema que todavía no hemos tratado, y es que la mayoría de los sistemas de ficheros tradicionales sólo pueden montarse en un equipo de forma concurrente. En muchos casos, los clústeres requieren de algún sistema de almacenamiento compartido con más propiedades, como por ejemplo los de servidores web de sitios dinámicos como WordPress, ya que necesitamos que el contenido servido sea el mismo en todos ellos. Por tanto, hacemos uso de los sistemas de ficheros distribuidos, de manera que cuando un nodo escribe, el cambio es reflejado en todos los nodos. Los sistemas de ficheros distribuidos utilizan su propio protocolo para comunicar el cliente y el servidor, proporcionando almacenamiento remoto de forma transparente, pues ellos lo verán como si de almacenamiento local se tratase. Incluyen tolerancia a fallos, gran escalabilidad, control de concurrencia… Entre los más conocidos se encuentran Lustre, Google FileSystem, GlusterFS… En este caso haremos uso de Ceph. Una vez comprendidos los términos indispensables, todo está listo para comenzar con la creación y configuración del escenario sobre el que trabajaremos. En este caso, he llevado a cabo la creación de las máquinas de forma previa, encontrándose las mismas sin ningún tipo de configuración, la cuál procederemos a realizar inicialmente con el cliente de comandos de OpenStack. La instalación de dicho cliente puede encontrarse detallada en anteriores artículos, por lo que podemos obviarla. Una vez dentro del mismo, podremos proceder a listar las instancias existentes en nuestro proyecto para así verificar que funciona correctamente. El comando a ejecutar sería: (openstackclient) alvaro@debian:~$ openstack server list +--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+ | ID | Name | Status | Networks | Image | Flavor | +--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+ | 30bdc36b-2fa6-4017-8ef4-2925122db4d8 | ceph-dns | ACTIVE | red de alvaro.vaca=10.0.0.17, 172.22.201.131 | Debian Buster 10.6 | m1.mini | | 468703b5-091d-4250-af14-ba40b6d1379b | ceph-client2 | ACTIVE | red de alvaro.vaca=10.0.0.19, 172.22.201.128 | Debian Buster 10.6 | m1.medium | | 6f47afc2-77ad-41a1-ab73-dc94f9141663 | ceph-client1 | ACTIVE | red de alvaro.vaca=10.0.0.9, 172.22.201.127 | Debian Buster 10.6 | m1.medium | | 7924631c-b9e6-4177-b894-88c5806d8735 | ceph-mon3 | ACTIVE | red de alvaro.vaca=10.0.0.14, 172.22.201.117 | Debian Buster 10.6 | m1.medium | | adca60ae-b729-4e6d-88c3-e47d7eca94d0 | ceph-mon2 | ACTIVE | red de alvaro.vaca=10.0.0.5, 172.22.201.115 | Debian Buster 10.6 | m1.medium | | c898f19d-482c-44b7-875e-380973697236 | ceph-mon1 | ACTIVE | red de alvaro.vaca=10.0.0.11, 172.22.201.59 | Debian Buster 10.6 | m1.medium | | 2bae6ab5-1820-4350-9555-e8ba3971dbde | ceph-osd3 | ACTIVE | red de alvaro.vaca=10.0.0.10, 172.22.200.184 | Debian Buster 10.6 | m1.medium | | 5d806b64-ee19-406e-a668-6c84110bbc42 | ceph-osd2 | ACTIVE | red de alvaro.vaca=10.0.0.15, 172.22.200.108 | Debian Buster 10.6 | m1.medium | | 9c14fb13-14e7-42d7-93e8-650cb2ebec1d | ceph-osd1 | ACTIVE | red de alvaro.vaca=10.0.0.13, 172.22.200.106 | Debian Buster 10.6 | m1.medium | | e6e150bc-ecc7-4af6-b2cf-bba60e9ff5b4 | ceph-admin | ACTIVE | red de alvaro.vaca=10.0.0.3, 172.22.200.103 | Debian Buster 10.6 | m1.normal | +--------------------------------------+--------------+--------+----------------------------------------------+--------------------+-----------+ Efectivamente, el cliente de OpenStack se encuentra actualmente operativo y nos ha mostrado la información referente a las 10 instancias creadas en mi proyecto, que como se puede apreciar, los sabores (flavors) asignados a las mismas se encuentran comprendidos entre los siguientes: m1.mini: vCPUs: 1 RAM: 512 MB m1.normal: vCPUs: 2 RAM: 1 GB m1.medium: vCPUs: 2 RAM: 2 GB Es importante asignar suficientes recursos a las máquinas, ya que tendrán que ejecutar varios servicios medianamente exigentes en cuanto a prestaciones. De forma adicional, he asociado 3 volúmenes de una capacidad de 5 GiB cada uno a las máquinas OSD, que tal y como posteriormente veremos, son las encargadas del almacenamiento en el clúster que montaremos. Vamos a verificar que dichos volúmenes han sido correctamente creados y asociados haciendo uso del comando: (openstackclient) alvaro@debian:~$ openstack volume list +--------------------------------------+---------+----------------+------+------------------------------------+ | ID | Name | Status | Size | Attached to | +--------------------------------------+---------+----------------+------+------------------------------------+ | be00866a-367e-4eea-94b9-7f979b510a88 | ceph-3 | in-use | 5 | Attached to ceph-osd3 on /dev/vdb | | e85306e6-77a1-44a4-96df-febd62ecd99f | ceph-2 | in-use | 5 | Attached to ceph-osd2 on /dev/vdb | | c8035f39-38b9-482e-9c22-3a262ed9cbf2 | ceph-1 | in-use | 5 | Attached to ceph-osd1 on /dev/vdb | +--------------------------------------+---------+----------------+------+------------------------------------+ De forma predeterminada, al crear una máquina en OpenStack se le asigna un grupo de seguridad con unas reglas de cortafuegos que en este caso no necesitamos, pues únicamente van a causarnos conflictos al intentar asignarles direcciones IP virtuales a algunas interfaces de red para el correcto funcionamiento de la alta disponibilidad, tal y como veremos durante el desarrollo del artículo. Para solventar dicho problema, utilizaremos una instrucción iterativa que procederá a deshabilitar el grupo de seguridad en todas las máquinas existentes en el proyecto, ejecutando para ello el comando: (openstackclient) alvaro@debian:~$ for i in admin osd{1..3} mon{1..3} client{1..2} dns &gt; do &gt; openstack server remove security group ceph-$i default &gt; done Esto no es todo, ya que también tenemos que deshabilitar la seguridad en los correspondientes puertos de las máquinas, pues el modus operandi por defecto de una máquina sin ningún grupo de seguridad asignado, es el de bloquear la conexión en todos los puertos asociados a la misma. Una vez más, utilizaremos una instrucción iterativa que deshabilitará la seguridad en los puertos existentes en las máquinas del proyecto, obteniendo en un primer paso la dirección IP de la máquina para posteriormente buscar el identificador del puerto asociado a dicha dirección. Finalmente, en una tercera instrucción, deshabilitará la seguridad en el mismo. Para ello, haremos uso del comando: (openstackclient) alvaro@debian:~$ for i in admin osd{1..3} mon{1..3} client{1..2} dns &gt; do &gt; ip=`openstack server list | egrep $i | egrep -o "10.0.0.[0-9]{1,3}"` &gt; port=`openstack port list | egrep $ip | awk '{ print $2 }'` &gt; openstack port set --disable-port-security $port &gt; done Listo, ya hemos deshabilitado la seguridad en los puertos, de manera que ya tenemos accesible todo el rango de puertos sin limitación alguna y podemos asociar direcciones IP virtuales a las interfaces de red en caso de así necesitarlo. Es importante mencionar que deshabilitar el cortafuegos es una acción un tanto precipitada, por lo que únicamente debe llevarse a cabo en un entorno controlado. Ya hemos realizado todas las acciones necesarias en el cliente de OpenStack, así que llega el momento de conectarnos a las 10 instancias para seguir configurándolas una por una… o quizás no, ya que existen herramientas de orquestación como Ansible que automatizan el aprovisionamiento de software, la gestión de configuraciones y el despliegue de aplicaciones en servidores. En otras palabras, Ansible permite a los DevOps gestionar sus servidores, configuraciones y aplicaciones de forma sencilla, robusta y paralela, ahorrando tiempo y esfuerzo. En caso de que la ejecución falle en uno de los servidores, se seguirá ejecutando de forma paralela sobre el resto. No debe importar las veces que ejecutemos Ansible ya que el resultado de una ejecución reiterada debe ser el mismo que el de una ejecución única (idempotencia). La gestión de los diferentes nodos se lleva a cabo utilizando SSH y únicamente requiere Python en el servidor remoto en el que se vaya a ejecutar para poder utilizarlo, paquete que generalmente suele venir instalado por defecto. A pesar de que este proyecto no está enfocado a enseñar a utilizar Ansible, considero que ha sido una herramienta de gran utilidad y que por tanto, es necesario mencionar aquellas características indispensables para poder aplicarlo a nuestro escenario, empezando por la instalación del mismo en la máquina controladora, la cual es recomendable llevar a cabo sobre un entorno virtual Python. En esta ocasión, el nombre que le voy a asignar al nuevo entorno virtual es ansible, así que lo generaré dentro de mi directorio donde almaceno todos los entornos virtuales, ejecutando para ello sobre una nueva terminal el comando: alvaro@debian:~$ python3 -m venv virtualenv/ansible Una vez creado, tendremos que iniciarlo haciendo uso de source con el binario activate que se encuentra contenido en el directorio bin/: alvaro@debian:~$ source virtualenv/ansible/bin/activate Una vez activado, ya podremos proceder a instalar dicha herramienta haciendo uso de pip, no sin antes actualizar dicho paquete en sí mismo, haciendo para ello uso del comando: (ansible) alvaro@debian:~$ pip install --upgrade pip Cuando el gestor de paquetes haya sido actualizado, podremos instalar el paquete en cuestión de nombre ansible, ejecutando para ello el comando: (ansible) alvaro@debian:~$ pip install ansible Nuestro nuevo entorno virtual se encuentra ahora equipado para hacer uso de la herramienta Ansible, sin embargo, dadas las características del proyecto que pretendemos llevar a cabo no nos será suficiente con las funcionalidades predeterminadas que incluye, de manera que necesitaremos instalar nuevas colecciones para así ampliar sus funciones. En este caso, las colecciones que instalaremos son ansible.posix y community.mysql, haciendo uso de los comandos: (ansible) alvaro@debian:~$ ansible-galaxy collection install ansible.posix (ansible) alvaro@debian:~$ ansible-galaxy collection install community.mysql Todo está listo para llevar a cabo la clonación del repositorio de GitHub que contiene el proyecto Ansible que utilizaremos para configurar de forma automática nuestro escenario, de manera que en mi caso me he movido al directorio GitHub/ (haciendo uso de cd), pues es donde almaceno todos los repositorios de GitHub, y por consecuencia, el que voy a clonar, ejecutando para ello el comando: (ansible) alvaro@debian:~/GitHub$ git clone git@github.com:alvarovaca/ansible-tfg.git Clonando en 'ansible-tfg'... remote: Enumerating objects: 40, done. remote: Counting objects: 100% (40/40), done. remote: Compressing objects: 100% (28/28), done. remote: Total 40 (delta 0), reused 37 (delta 0), pack-reused 0 Recibiendo objetos: 100% (40/40), 9.32 KiB | 9.32 MiB/s, listo. Una vez completada la clonación, me he movido al directorio ansible-tfg/ resultante (haciendo uso de cd), procediendo ahora a listar el contenido del mismo de forma recursiva y arborescente, para así poder apreciar de una manera mucho más gráfica de lo que se compone nuestro proyecto Ansible, haciendo para ello uso del comando: (ansible) alvaro@debian:~/GitHub/ansible-tfg$ tree . ├── ansible.cfg ├── group_vars │   └── all ├── hosts ├── post.yml ├── pre.yml ├── README.md └── roles ├── ceph │   ├── files │   │   ├── ansible_rsa │   │   └── ansible_rsa.pub │   └── tasks │   └── main.yml ├── common │   ├── handlers │   │   └── main.yml │   └── tasks │   └── main.yml ├── dns │   ├── files │   │   ├── named.conf.local │   │   └── named.conf.options │   ├── handlers │   │   └── main.yml │   ├── tasks │   │   └── main.yml │   └── templates │   └── db.example.com.j2 └── pacemaker ├── files │   └── prestashop.conf ├── handlers │   └── main.yml └── tasks └── main.yml 17 directories, 19 files Como se puede apreciar, existen varios directorios que contienen a su vez ficheros, pero no hay de qué preocuparse, ya que los cambios a realizar sobre dicho proyecto para poder adaptarlo a cada caso son prácticamente mínimos y no requieren ningún tipo de conocimiento sobre Ansible. Existen diferentes maneras de decirle a Ansible qué servidores debe gestionar. La más fácil es añadir nuestras máquinas separadas por grupos a un inventario de nombre hosts, dependiendo del ámbito al que estén dirigidas. En mi caso, el resultado final sería el siguiente (sustituir las direcciones IP por las correspondientes alcanzables): (ansible) alvaro@debian:~/GitHub/ansible-tfg$ cat hosts [admin] admin1 ansible_host=172.22.200.103 [osd] osd1 ansible_host=172.22.200.106 osd2 ansible_host=172.22.200.108 osd3 ansible_host=172.22.200.184 [mon] mon1 ansible_host=172.22.201.59 mon2 ansible_host=172.22.201.115 mon3 ansible_host=172.22.201.117 [client] client1 ansible_host=172.22.201.127 client2 ansible_host=172.22.201.128 [dns] dns1 ansible_host=172.22.201.131 De forma complementaria, es recomendable generar un directorio de nombre group_vars en el que se incluyan todas las variables que apliquen a determinados grupos pertenecientes al inventario, que posteriormente podrán utilizarse dentro de plantillas o ejecución de plays (conjunto ordenado de tareas que se ejecutan). En este caso, he generado un fichero de nombre all dentro del directorio previamente mencionado en el que se almacenarán las variables visibles para todos los nodos del proyecto, existiendo en este caso las siguientes: xxx_ip: Dirección IP privada de cada uno de los nodos del proyecto. Es necesario cambiarlas para adaptarlas a cada caso. xxx_virt_ip: Dirección IP virtual que será asignada a cada uno de los recursos que se configurarán en alta disponibilidad. Puede modificarse pero no es necesario. hacluster_crypt_pass: Contraseña encriptada para el usuario hacluster. Puede modificarse pero no es necesario. hacluster_plain_pass: Contraseña en texto plano para el usuario hacluster. Puede modificarse pero no es necesario. (ansible) alvaro@debian:~/GitHub/ansible-tfg$ cat group_vars/all admin_ip: 10.0.0.3 osd1_ip: 10.0.0.13 osd2_ip: 10.0.0.15 osd3_ip: 10.0.0.10 mon1_ip: 10.0.0.11 mon2_ip: 10.0.0.5 mon3_ip: 10.0.0.14 client1_ip: 10.0.0.9 client2_ip: 10.0.0.19 dns_ip: 10.0.0.17 apache_virt_ip: 10.0.0.200 mariadb_virt_ip: 10.0.0.201 hacluster_crypt_pass: $6$vs272OD3toORiva2$SNDSrdhEDPfWo28ZoTmrC21NWVxoueRfuYbatNGprsv.PY6KpOQEV/zYYsfiwGTWkCCVogEp3WSgFnA0I3b5J/ hacluster_plain_pass: hacluster Por último, en el fichero de nombre ansible.cfg podemos incluir parámetros de configuración comunes a todo el proyecto, como por ejemplo el usuario remoto que se utilizará en las máquinas a las que vamos a conectarnos, la ruta a la clave privada para la conexión SSH… (ansible) alvaro@debian:~/GitHub/ansible-tfg$ cat ansible.cfg [defaults] inventory = hosts remote_user = debian host_key_checking = False deprecation_warnings = False private_key_file = /home/alvaro/.ssh/linux.pem ansible_python_interpreter = /usr/bin/python3 Los proyectos Ansible suelen organizarse en forma de roles en los que se definen una serie de tareas ordenadas a realizar (play). Por último, se genera un playbook en el que se indicará la correspondencia entre los roles y las máquinas o grupos de máquinas del inventario a los que aplicar dichas tareas. En este caso, disponemos de un playbook inicial de nombre pre.yml que hace uso de un total de 3 roles previamente definidos, aplicando las siguientes tareas sobre determinados grupos de máquinas: all: Ensure apt does not use debian replica: Elimina cualquier repositorio de debian.org del fichero /etc/apt/sources.list. Ensure apt uses cica replica: Añade los repositorios de cica.es al fichero /etc/apt/sources.list para evitar problemas con el proxy. Ensure system is updated: Actualiza la paquetería instalada. Set timezone to Europe/Madrid: Establece la zona horaria a Europa/Madrid. Ensure hosts file is not managed by cloud_init: Evita que cloud init gestione el fichero /etc/hosts, haciendo que perdure. Ensure hosts file does not resolve hostname: Elimina la línea referente a la resolución del hostname en el fichero /etc/hosts, delegando dicha tarea al servidor DNS. Add DNS server to resolvconf configuration: Añade la dirección IP del servidor DNS al fichero /etc/resolvconf/resolv.conf.d/head. Add search pattern to resolvconf configuration: Añade la cadena “search example.com” al fichero /etc/resolvconf/resolv.conf.d/base. restart resolvconf: Reinicia el servicio resolvconf, generando así un fichero /etc/resolv.conf acorde a las configuraciones previamente realizadas. dns: Ensure bind9 is installed: Instala el paquete bind9. Copy named.conf.options file: Copia el fichero named.conf.options a /etc/bind/. Copy named.conf.local file: Copia el fichero named.conf.local a /etc/bind/. Copy db.example.com zone using template: Utiliza una plantilla para generar un fichero de nombre /var/cache/bind/db.example.com. restart bind9: Reinicia el servicio bind9, surtiendo así efecto las configuraciones previamente realizadas. admin, osd, mon, client: Create cephuser user: Crea un usuario cephuser con un directorio personal. Allow cephuser to execute sudo commands without password: Añade una línea al fichero /etc/sudoers para permitir al usuario cephuser ejecutar comandos sudo sin contraseña. Create .ssh structure: Crea el directorio .ssh en el directorio personal del usuario cephuser. Copy ansible_rsa private key: Copia una clave privada previamente generada a la ruta .ssh/id_rsa. Copy ansible_rsa public key: Copia una clave pública previamente generada a la ruta .ssh/id_rsa.pub. Copy authorized_keys file: Copia la clave pública previamente generada a la ruta .ssh/authorized_keys. Check if known_hosts file exists: Comprueba si el fichero .ssh/known_hosts existe, para que en caso de que no, realizar la siguiente tarea. Scan and add SSH keys of the machines to known_hosts file: Genera el fichero .ssh/known_hosts y escanea y añade las claves SSH del resto de máquinas al mismo. Change known_hosts file owner: Establece correctamente el propietario y grupo del fichero .ssh/known_hosts. Add Ceph apt key: Añade la clave apt de Ceph. Add Ceph repository: Añade el repositorio de Ceph. Ensure needed packages are installed: Instala la paquetería necesaria para el correcto funcionamiento. Por último, procederemos a ejecutar dicho playbook para así llevar a cabo las tareas previamente mencionadas sobre las máquinas: (ansible) alvaro@debian:~/GitHub/ansible-tfg$ ansible-playbook pre.yml PLAY [all] *************************************************************************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************************************************** ok: [mon1] ok: [admin1] ok: [osd1] ok: [osd3] ok: [osd2] ok: [mon2] ok: [mon3] ok: [dns1] ok: [client2] ok: [client1] ... PLAY RECAP *************************************************************************************************************************************************************************** admin1 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 client1 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 client2 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 dns1 : ok=16 changed=14 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 mon1 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 mon2 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 mon3 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 osd1 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 osd2 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 osd3 : ok=23 changed=20 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Tras alrededor de 30 minutos, todas las tareas se han completado correctamente, tal y como podemos apreciar en el resumen mostrado al final de la ejecución del playbook. Dado el tamaño de la salida por pantalla resultante de dicha ejecución, he decidido recortarla para no ensuciar demasiado. No obstante, es posible encontrarla aquí al completo. Antes de continuar, vamos a producir un reinicio para así cargar en memoria la nueva versión del núcleo instalada en las máquinas, volviendo para ello a nuestra terminal con el entorno virtual del cliente OpenStack y ejecutando la siguiente instrucción iterativa, que reiniciará todas las máquinas existentes: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ for i in admin osd{1..3} mon{1..3} client{1..2} dns &gt; do &gt; openstack server reboot ceph-$i &gt; done Una vez ejecutada la instrucción y mientras esperamos a que todas las máquinas terminen de arrancar, vamos a realizar un par de gestiones necesarias para el posterior desarrollo del artículo. La primera de ellas consiste en listar las redes existentes en nuestro proyecto, haciendo para ello uso del comando: (openstackclient) alvaro@debian:~$ openstack network list +--------------------------------------+--------------------+----------------------------------------------------------------------------+ | ID | Name | Subnets | +--------------------------------------+--------------------+----------------------------------------------------------------------------+ | 2526bd3a-3c01-40b3-b37d-605d99adb980 | red de alvaro.vaca | 747952ea-a0a0-4bd5-8ad6-8dec0666a299 | | 49812d85-8e7a-4c31-baa2-d427692f6568 | ext-net | 158bbe3e-3c98-485e-8042-ba6402111ea6, 6218710b-aa05-46f7-b198-7639efe3da95 | +--------------------------------------+--------------------+----------------------------------------------------------------------------+ Como se puede apreciar, en mi proyecto existen un total de 2 redes, una interna y una externa, sin embargo, la primera de ellas contiene a su vez una subred con el direccionamiento 10.0.0.0/24, pudiendo observar el identificador de la misma. Por último, vamos a generar una IP flotante dentro del rango enrutable de forma directa desde nuestra máquina, es decir, en la red externa, que como podemos apreciar en la salida del comando superior, tiene asociado el identificador 49812d85-8e7a-4c31-baa2-d427692f6568, de manera que haremos uso del comando: (openstackclient) alvaro@debian:~$ openstack floating ip create 49812d85-8e7a-4c31-baa2-d427692f6568 +---------------------+--------------------------------------+ | Field | Value | +---------------------+--------------------------------------+ | created_at | 2021-06-10T09:26:30Z | | description | | | dns_domain | None | | dns_name | None | | fixed_ip_address | None | | floating_ip_address | 172.22.200.28 | | floating_network_id | 49812d85-8e7a-4c31-baa2-d427692f6568 | | id | aeffdcb1-1c4d-4471-b21e-fb606ceb30de | | name | 172.22.200.28 | | port_details | None | | port_id | None | | project_id | dab5156c32654875b6c54ce23c6712a2 | | qos_policy_id | None | | revision_number | 1 | | router_id | None | | status | DOWN | | subnet_id | None | | tags | [] | | updated_at | 2021-06-10T09:26:30Z | +---------------------+--------------------------------------+ La ejecución del comando ha resultado en la efectiva creación de una IP flotante, concretamente la 172.22.200.28, sin embargo, vamos a verificarlo listando para ello todas las direcciones IP flotantes asociadas a nuestro proyecto, ejecutando para ello el comando: (openstackclient) alvaro@debian:~$ openstack floating ip list +--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+ | ID | Floating IP Address | Fixed IP Address | Port | Floating Network | Project | +--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+ | 0805e916-61e4-4c1c-bd82-276c2f463b6e | 172.22.201.128 | 10.0.0.19 | ad391091-7078-49c9-8032-9f06059c3534 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 360c3f8c-3677-410d-b994-992d1e1c7edd | 172.22.200.106 | 10.0.0.13 | f9d30785-3b39-439d-a906-1a33ba8b6965 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 3d54a154-9bbe-4ea3-bdf5-d447cbf9fa20 | 172.22.201.115 | 10.0.0.5 | 92a26bd0-85a3-4bec-9a23-b1bfa04b7dee | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 622afc06-92ed-4862-bfbb-531b1a0b9984 | 172.22.201.131 | 10.0.0.17 | 95eace1b-8732-416c-8c7b-f4902fb140f6 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 63fc1fc7-c73d-4eb7-905e-e017fe1daa49 | 172.22.201.127 | 10.0.0.9 | 69b86694-a9cd-47d6-bf4f-250dcee41838 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 6d48bab1-5d45-49ac-95c8-1475d2f00e79 | 172.22.201.117 | 10.0.0.14 | d308a511-871f-496d-9944-107ecdab57fb | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 6f45b92d-4130-4090-969d-f29eb761eb04 | 172.22.200.103 | 10.0.0.3 | b9db108e-c418-416a-826d-309510ddeb4f | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | 83ba8bca-f18c-4f9a-8d0d-eb710ca07c7e | 172.22.200.184 | 10.0.0.10 | c93827ca-a219-4dcd-898a-7b5065c27c63 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | aeffdcb1-1c4d-4471-b21e-fb606ceb30de | 172.22.200.28 | None | None | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | f51c0df9-75ec-4d54-8af1-49f8972f49fb | 172.22.201.59 | 10.0.0.11 | 54f10e76-4470-4909-acc1-02376a3b09f2 | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | | ffcfab43-9023-41bb-be96-63be788bfd51 | 172.22.200.108 | 10.0.0.15 | d967ec3f-6c62-491d-9f1a-0d6c94921c4c | 49812d85-8e7a-4c31-baa2-d427692f6568 | dab5156c32654875b6c54ce23c6712a2 | +--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+ Como era de esperar, la IP flotante ha sido correctamente asignada a nuestro proyecto y se encuentra actualmente disponible para ser asociada a una dirección IP fija del rango de la red interna. Tras ello, estableceremos una conexión SSH con la máquina ceph-admin, la cual utilizaremos para realizar la mayor parte de las gestiones restantes, haciendo uso del comando: (openstackclient) alvaro@debian:~$ ssh debian@172.22.200.103 -i /home/alvaro/.ssh/linux.pem Linux ceph-admin 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. La conexión SSH se ha establecido correctamente, sin embargo, antes de proceder, considero necesario explicar qué es lo que viene a continuación. Tal y como previamente hemos mencionado, necesitamos un sistema de ficheros distribuido para desplegar en alta disponibilidad cualquier aplicación que requiera compartir el almacenamiento entre los diferentes nodos sobre los que se ejecutará la misma, como es este caso. La solución open source de la que haremos uso es conocida como Ceph. Se basa en objetos binarios y evita en todo momento las rígidas jerarquías de los sistemas convencionales. Para el almacenamiento utilizamos discos duros, como es lógico, sin embargo, un algoritmo se encarga de gestionar los objetos binarios, que están divididos en numerosas partes y repartidos entre muchos servidores de forma pseudoaleatoria (nunca almacenando más de una copia en el mismo nodo), que posteriormente vuelven a unificarse. Al tener una aplicación de creciente tamaño, no sabemos cuál será la cantidad de datos a gestionar en un futuro, por tanto, el sistema de almacenamiento debe poder ampliarse fácilmente sin dejar de funcionar, mediante servidores adicionales. El cliente lo percibirá en todo momento como un sencillo directorio de un sistema de archivos tradicional, sin necesidad de que el mismo conozca absolutamente nada sobre la distribución de los mismos. Otra de las características indispensables es la redundancia de los datos, ya que de nada nos sirve tener los datos distribuidos incluso en diferentes áreas geográficas del planeta si un simple fallo en uno de los discos puede causarnos la corrupción total o parcial de los datos almacenados. Por ello, el sistema de autogestión y autorrecuperación de Ceph reduce dramáticamente las interrupciones, convirtiéndolo en una excelente elección para las empresas. Facebook y Dropbox son dos de los ejemplos más influyentes. Aunque en este caso no vamos a hacer uso de la siguiente característica, me parece curioso mencionar que las aplicaciones pueden acceder a Ceph Object Storage a través de una interfaz RESTful que admite las API de Amazon S3 y Openstack Swift. Entrando un poco más en profundidad, el sistema consta de una red de demonios que se ejecutan entre los diferentes nodos constituyentes del clúster: Monitor (MONs): Gestionan en todo momento el estado de cada uno de los nodos en el clúster, informando de fallos lo antes posible. Se recomienda disponer de al menos tres monitor nodes. Manager (MGRs): Gestionan la utilización del espacio, la carga del sistema y el nivel de utilización de los nodos. Object Storage Devices (OSDs): Son los servicios que realmente gestionan los archivos: son responsables del almacenamiento, el duplicado y la restauración de los datos. Se recomienda tener al menos tres OSDs en el cluster. Se ejecuta un OSD por cada disco. Metadata (MDs): Se encargan de almacenar metadatos por motivos de rendimiento, como rutas de almacenamiento, sellos de tiempo y nombres de los archivos. El componente clave del almacenamiento de datos es el algoritmo CRUSH (Controlled Replication Under Scalable Hashing). Este algoritmo es capaz de encontrar un OSD con el archivo solicitado gracias a una tabla de asignaciones almacenada en los MDs. Por si no era suficiente, al nivel del OSD se utiliza un journaling o registro de cambios con fecha y hora, dificultando por tanto la corrupción de los datos. Allí se guardan temporalmente los archivos que se pretenden almacenar mientras se espera a que se ubiquen correctamente en todos los OSD previstos. No todo podía ser bonito, y es que como se puede apreciar, para utilizar este software necesitamos una red relativamente “grande” para así poder albergar de forma redundante los componentes. También se necesitan unos conocimientos suficientes, dada su complejidad. Su compatibilidad se limita a sistemas Linux, aunque estoy seguro que esto último no es un problema para nosotros. Una vez comprendido de forma superficial el funcionamiento de Ceph, vamos a proceder a crear un clúster, cambiándonos para ello al usuario cephuser, pues tiene los privilegios necesarios para ejecutar comandos sin contraseña y realizar conexiones SSH al resto de máquinas. Para ello, haremos uso del comando: debian@ceph-admin:~$ sudo su - cephuser El siguiente paso consistirá en generar un directorio en el que se almacenarán los ficheros resultantes del clúster. Su nombre no es relevante aunque es recomendable que sea significativo para evitar eliminarlo por error, de manera que en mi caso lo generaré y accederé al mismo ejecutando para ello el comando: cephuser@ceph-admin:~$ mkdir cephcluster &amp;&amp; cd cephcluster/ Para el tercero de los pasos haremos uso del binario ceph-deploy previamente instalado durante la ejecución del playbook de Ansible, que nos permitirá llevar a cabo de forma automatizada el proceso de creación del clúster. Para comenzar, tendremos que indicar el nombre de las máquinas que actuarán como monitor nodes. El comando final del que haré uso será: cephuser@ceph-admin:~/cephcluster$ ceph-deploy new ceph-mon{1..3} En consecuencia de la ejecución de dicho comando se habrán generado un total de 3 ficheros en el directorio actual, de manera que procederemos a verificarlo listando para ello el contenido existente mediante la ejecución del comando: cephuser@ceph-admin:~/cephcluster$ ls -l total 16 -rw-r--r-- 1 cephuser cephuser 237 Jun 9 14:47 ceph.conf -rw-r--r-- 1 cephuser cephuser 5476 Jun 9 14:47 ceph-deploy-ceph.log -rw------- 1 cephuser cephuser 73 Jun 9 14:47 ceph.mon.keyring De todos los ficheros existentes únicamente nos importa el primero de ellos, de nombre ceph.conf, cuya finalidad es la de albergar parámetros de configuración del clúster, pues el segundo almacena logs que por ahora no son relevantes y el tercero, una clave que utilizarán los monitor nodes pertenecientes al clúster para autenticarse, que será distribuida a los mismos de forma totalmente automatizada. Por defecto, Ceph crea un total de 3 réplicas de los datos almacenados (en este caso, una en cada OSD), lo que a grosso modo podemos llamar un RAID-1. Como consecuencia, el tamaño total disponible y útil sería aproximadamente el de uno de los discos, ya que disponemos de 3 discos de 5 GiB, pero los otros 2 se limitarían a almacenar una copia (es decir, 1/3 del total disponible). Con dicho valor predeterminado, podrían dejar de funcionar un máximo de 2 de 3 OSD, ya que la información seguiría existiendo en aquel nodo funcional. Por ello, vamos a modificar dicho valor y a establecer el número de copias en 2, pudiendo aprovechar por tanto una mayor cantidad de espacio, pero sacrificando como es lógico, seguridad, pues únicamente podría morir 1 de 3 OSD, haciendo uso del comando: cephuser@ceph-admin:~/cephcluster$ echo "osd pool default size = 2" &gt;&gt; ceph.conf Para verificar que la línea indicada se ha introducido correctamente dentro del fichero en cuestión, vamos a proceder a visualizar el contenido del mismo, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ cat ceph.conf [global] fsid = 073817c1-bd48-451e-ad7a-2fe769245057 mon_initial_members = ceph-mon1, ceph-mon2, ceph-mon3 mon_host = 10.0.0.11,10.0.0.5,10.0.0.14 auth_cluster_required = cephx auth_service_required = cephx auth_client_required = cephx osd pool default size = 2 Efectivamente, la línea especificada ha sido correctamente introducida, además de los monitor nodes junto con sus correspondientes direcciones IP. En un entorno más real, se utilizarían dos redes diferentes, una pública que sería indicada mediante la directiva public network que es aquella a través de la cual los clientes van a hacer uso del sistema de ficheros y una privada que sería indicada mediante la directiva private network que es aquella a través de la cual los OSD realizarán las replicaciones de datos, que debe contar con un ancho de banda considerable, para evitar cuellos de botella durante las mismas. En este caso, al únicamente existir una red, no es necesario realizar distinción. El quinto paso consiste en llevar a cabo la instalación del software en todos los nodos pertenecientes al clúster. En este caso, la instalación la realizaremos sobre 9 de las 10 máquinas (ya que el servidor DNS no pertenece al clúster como tal), haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy install ceph-admin ceph-mon{1..3} ceph-osd{1..3} ceph-client{1..2} La instalación puede tomar unos minutos en completarse, consistiendo el siguiente paso en llevar a cabo las gestiones necesarias en los 3 monitor nodes previamente indicados, como por ejemplo establecer el quorum, que será el encargado de determinar por mayoría el estado de los nodos, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy mon create-initial El séptimo paso refiere a la copia del fichero de configuración y la clave de administración en todos y cada uno de los nodos pertenecientes al clúster para así poder utilizar el intérprete de comandos de Ceph desde cualquiera de las máquinas y sin necesidad de indicar ningún parámetro adicional. Para ello, haremos uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy admin ceph-admin ceph-mon{1..3} ceph-osd{1..3} ceph-client{1..2} Una vez finalizada la transferencia a todos los nodos, tendremos que ajustar el propietario del fichero /etc/ceph/ceph.client.admin.keyring a cephuser para que así pueda hacer uso del mismo. Comenzaremos por realizar dicha modificación en la máquina actual, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ sudo chown cephuser:cephuser /etc/ceph/ceph.client.admin.keyring Tras ello, tendremos que llevar a cabo la misma acción sobre el resto de máquinas, sin embargo, podemos automatizarlo con una pequeña estructura iterativa, que quedará de la siguiente forma: cephuser@ceph-admin:~/cephcluster$ for i in mon{1..3} osd{1..3} client{1..2} &gt; do &gt; ssh cephuser@ceph-$i "sudo chown cephuser:cephuser /etc/ceph/ceph.client.admin.keyring" &gt; done Posteriormente estableceremos los demonios de los manager, que por recomendación por parte de la documentación de Ceph, deberían ser las mismas máquinas que albergan el demonio monitor. Para ello, haremos uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy mgr create ceph-mon{1..3} Una vez desplegados los demonios manager, es hora de configurar los OSD, así que de forma previa a ello, vamos a verificar que los 3 nodos destinados a dicha finalidad tengan correctamente asociado un volumen secundario de 5 GiB, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy disk list ceph-osd{1..3} ... [ceph-osd1][INFO ] Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors [ceph-osd1][INFO ] Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors ... [ceph-osd2][INFO ] Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors [ceph-osd2][INFO ] Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors ... [ceph-osd3][INFO ] Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors [ceph-osd3][INFO ] Disk /dev/vdb: 5 GiB, 5368709120 bytes, 10485760 sectors Como era de esperar, en las tres máquinas existe un volumen en /dev/vdb de 5 GiB, así que procederemos a implementar la funcionalidad de OSD en dichas máquinas, utilizando para ello las rutas a los discos vacíos, en los que se crearán las particiones de datos y journal, haciendo para ello uso de los comandos: cephuser@ceph-admin:~/cephcluster$ ceph-deploy osd create --data /dev/vdb ceph-osd1 cephuser@ceph-admin:~/cephcluster$ ceph-deploy osd create --data /dev/vdb ceph-osd2 cephuser@ceph-admin:~/cephcluster$ ceph-deploy osd create --data /dev/vdb ceph-osd3 Los tres volúmenes han sido correctamente particionados y se encuentran listos para su uso, sin embargo, todavía nos falta un elemento esencial en nuestro clúster: los MDs. Dado que “únicamente” contamos con 9 máquinas, tendremos que alojar dichos demonios en las mismas máquinas que ejecutan los OSD, aunque en un caso práctico real, sería mucho más óptimo tenerlo lo más distribuido posible. Para ello, ejecutaremos el comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy mds create ceph-osd{1..3} Una vez finalizada la ejecución del comando tendremos todos los componentes necesarios para el correcto funcionamiento del clúster activos y configurados. Para hacer uso de CephFS necesitamos un mínimo de 2 pools, uno para datos y otro para metadatos. Dado el frecuente acceso a los mismos, es recomendable que se utilicen discos de baja latencia para aumentar así la eficiencia de lectura y escritura. Para la generación de dichos pools, ejecutaremos los siguientes comandos: cephuser@ceph-admin:~/cephcluster$ ceph osd pool create cephfs_data 64 cephuser@ceph-admin:~/cephcluster$ ceph osd pool create cephfs_metadata 64 Una vez finalizada la ejecución de los comandos, vamos a verificar que los pools se han generado correctamente en las correspondientes máquinas, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph osd lspools 1 device_health_metrics 2 cephfs_data 3 cephfs_metadata Efectivamente, se han generado 2 nuevos pools con los nombres cephfs_data y cephfs_metadata, habiéndoles sido asignados los identificadores 2 y 3, respectivamente. El último paso consiste en crear un sistema de ficheros en el que podremos empezar a alojar objetos, que hará uso de los 2 pools creados con anterioridad, con nombre cephfs, por ejemplo. El comando a ejecutar sería: cephuser@ceph-admin:~/cephcluster$ ceph fs new cephfs cephfs_metadata cephfs_data new fs with metadata pool 3 and data pool 2 Finalmente vamos a verificar que el sistema de ficheros se ha generado correctamente en las correspondientes máquinas, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph fs ls name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ] Como era de esperar, se ha generado un nuevo sistema de ficheros de nombre cephfs que hace uso del pool de datos cephfs_data y del pool de metadatos cephfs_metadata. Adicionalmente, vamos a verificar el estado general del clúster para así comprobar que todos los pasos llevados a cabo han sido efectivos y no ha ocurrido ningún problema, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph health HEALTH_OK Efectivamente, el estado general del clúster es correcto (HEALTH_OK), sin embargo, la información mostrada es muy limitada, de manera que vamos a profundizar un poco más haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_OK services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 7m) mgr: ceph-mon1(active, since 7m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 2 standby osd: 3 osds: 3 up (since 6m), 3 in (since 11m) data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 22 objects, 2.8 KiB usage: 47 MiB used, 15 GiB / 15 GiB avail pgs: 81 active+clean Como se puede apreciar de una forma más concreta en la salida del comando, contamos con un total de 3 pools y los servicios actualmente activos se encuentran alojados en las siguientes máquinas: mon: ceph-mon1, ceph-mon2 y ceph-mon3 mgr: ceph-mon1, ceph-mon2 y ceph-mon3 mds: ceph-osd1, ceph-osd2 y ceph-osd3 osd: ceph-osd1, ceph-osd2 y ceph-osd3 Las labores en nuestro clúster Ceph han finalizado, encontrándose a partir de ahora totalmente disponible para albergar ficheros en su interior, así que es hora de explicar todo lo referente al despliegue de la aplicación sobre dicho clúster de almacenamiento distribuido, sobre el que también utilizaremos software para asegurar la alta disponibilidad de los servicios relacionados. En este caso, vamos a utilizar Pacemaker, cuya finalidad es la de controlar y coordinar las máquinas del clúster junto a Corosync, cuya finalidad es la de permitir la comunicación entre las máquinas pertenecientes al mismo y enviar órdenes a Pacemaker. Pacemaker intenta gestionar de la mejor manera posible la distribución de los recursos, teniendo en cuenta factores como el uso de recursos de cada uno de los nodos. A pesar de ello, es posible forzar las colocaciones de los recursos en caso de así necesitarlo, por ejemplo, cuando un nodo tiene más recursos que otro y preferimos que la ejecución se lleve a cabo sobre el mismo siempre que sea posible. Para interconectar las máquinas que ejecutan los servicios necesarios para el despliegue de la aplicación y tienen acceso al sistema de ficheros previamente generado, necesitaremos un usuario de nombre hacluster cuya contraseña sea la misma en ambas máquinas para así posibilitar la comunicación entre las mismas (ceph-client1 y ceph-client2). Vamos a desplegar un total de 4 recursos: WebVirtualIP: Dirección IP virtual (10.0.0.200) que se asignará de forma dinámica a cualquiera de los dos nodos, dependiendo de su disponibilidad. WebSite: Encargado de gestionar el demonio de apache2 y que irá de la mano con el recurso anterior, ya que deben estar siempre ejecutándose en la misma máquina. DBVirtualIP: Dirección IP virtual (10.0.0.201) que se asignará de forma dinámica a cualquiera de los dos nodos, dependiendo de su disponibilidad. Database: Encargado de gestionar el demonio de mariadb y que irá de la mano con el recurso anterior, ya que deben estar siempre ejecutándose en la misma máquina. Quizás puede llegar a ser algo confuso, así que para aclarar un poco las ideas, vamos a ir haciéndolo y entendiéndolo sobre la marcha. Para ello, vamos a volver a nuestra terminal con el entorno virtual ansible y vamos a ejecutar el playbook de nombre post.yml que hace uso de un rol previamente definido, aplicando las siguientes tareas sobre el siguiente grupo de máquinas: client: Ensure needed packages are installed: Instala la paquetería necesaria para el correcto funcionamiento. disable apache2: Para y deshabilita el servicio apache2, ya que a partir de ahora será gestionado por Pacemaker. disable mariadb: Para y deshabilita el servicio mariadb, ya que a partir de ahora será gestionado por Pacemaker. Obtain secret key and save as a variable: Busca la clave secreta en el fichero /etc/ceph/ceph.client.admin.keyring y la almacena en una variable. Store the secret key in admin.secret file: Almacena la clave secreta en un fichero de nombre /home/cephuser/admin.secret con los permisos apropiados. Mount CephFS on /ceph and add to fstab: Monta el sistema de ficheros en /ceph y añade una entrada al /etc/fstab. Create the necessary structure on /ceph: Crea los subdirectorios /ceph/sql y /ceph/prestashop. Check if /ceph/prestashop folder is empty before proceeding: Comprueba si el directorio /ceph/prestashop está vacío, para en ese caso, realizar las 3 siguientes tareas. Download and unzip prestashop_1.7.7.0.zip package: Descarga y descomprime el paquete de PrestaShop 1.7.7.0 en el directorio /ceph/prestashop. Unzip prestashop.zip package: Descomprime el paquete resultante de la previa descompresión en el mismo directorio, estableciendo correctamente los permisos y propietarios. Delete unnecessary PrestaShop files: Elimina los ficheros Install_PrestaShop.html y prestashop.zip dada su nula utilidad. Copy prestashop.conf VirtualHost file: Copia el fichero prestashop.conf a /etc/apache2/sites-available/. Create prestashop.conf VirtualHost symlink: Crea un enlace simbólico al fichero anterior en /etc/apache2/sites-enabled/. Delete 000-default.conf VirtualHost symlink: Elimina el enlace simbólico en /etc/apache2/sites-enabled/000-default.conf. Create rewrite module symlink: Crea un enlace simbólico al fichero /etc/apache2/mods-available/rewrite.load en /etc/apache2/mods-enabled/. Change MariaDB datadir to /ceph/sql: Modifica el fichero /etc/mysql/mariadb.conf.d/50-server.cnf y cambia el datadir a /ceph/sql. Change MariaDB bind-address to 0.0.0.0: Modifica el fichero /etc/mysql/mariadb.conf.d/50-server.cnf y cambia la bind-address a 0.0.0.0. Check if /ceph/sql folder is empty before proceeding: Comprueba si el directorio /ceph/sql está vacío, para en ese caso, realizar la siguiente tarea. Install DB prerequisites: Instala los ficheros necesarios en el directorio /ceph/sql para poder utilizarlo como datadir. Set hacluster user password: Establece una contraseña común para el usuario hacluster. Destroy default cluster: Elimina el clúster de Pacemaker que se genera por defecto. Add hosts to the new cluster: Añade ambos nodos al nuevo clúster de Pacemaker. Create, start and enable the new cluster: Crea, inicia y habilita el nuevo clúster de Pacemaker que acabamos de definir. Disable STONITH property: Deshabilita la propiedad STONITH, para evitar conflictos en el quorum. Create VirtualIP resource for Apache2: Crea el recurso de nombre WebVirtualIP. Create Apache2 resource: Crea el recurso de nombre WebSite. Create Apache2 and VirtualIP colocation: Crea una colocación para que los anteriores recursos se ejecuten en el mismo nodo. Create VirtualIP resource for MariaDB: Crea el recurso de nombre DBVirtualIP. Create MariaDB resource: Crea el recurso de nombre Database. Create MariaDB and VirtualIP colocation: Crea una colocación para que los anteriores recursos se ejecuten en el mismo nodo. Create constraint to preferably run all resources on the first node: Crea una restricción para que preferiblemente se ejecuten todos los recursos en el nodo ceph-client1. Create a new MariaDB database: Crea una nueva base de datos de nombre prestashop. Create a new user with privileges in the previous database: Crea un nuevo usuario de nombre usuario y contraseña usuario que cuenta con los privilegios suficientes sobre la base de datos previamente generada. Por último, procederemos a ejecutar dicho playbook para así llevar a cabo las tareas previamente mencionadas sobre las máquinas: (ansible) alvaro@debian:~/GitHub/ansible-tfg$ ansible-playbook post.yml PLAY [client] ************************************************************************************************************************************************************************ TASK [Gathering Facts] *************************************************************************************************************************************************************** ok: [client1] ok: [client2] ... PLAY RECAP *************************************************************************************************************************************************************************** client1 : ok=34 changed=31 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 client2 : ok=13 changed=12 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 Tras alrededor de 15 minutos, todas las tareas se han completado correctamente, tal y como podemos apreciar en el resumen mostrado al final de la ejecución del playbook. Dado el tamaño de la salida por pantalla resultante de dicha ejecución, he decidido recortarla para no ensuciar demasiado. No obstante, es posible encontrarla aquí al completo. Nuestra aplicación ya ha sido desplegada correctamente, sin embargo, existe un problema que posiblemente no habíais planteado hasta ahora. La dirección IP virtual a través de la cual accederemos al servicio web es una IP dentro de un rango interno de mi proyecto de OpenStack, lo que imposibilita su acceso de forma directa, ya que no es una red enrutable. Sin embargo, existe una posibilidad un tanto “retorcida” que consiste en crear un balanceador de carga en OpenStack con una IP flotante dentro del rango alcanzable y asociarlo a dicha IP interna, de manera que a través de una especie de DNAT, conseguiríamos acceder al servidor web desde el exterior. El inconveniente es que el cliente de línea de comandos de OpenStack que hemos estado utilizando hasta ahora no nos sirve, ya que la versión que se está utilizando no soporta dicha característica, de manera que tendremos que instalar en un nuevo entorno virtual el cliente del elemento de OpenStack responsable de la gestión de las redes, neutron. En esta ocasión, el nombre que le voy a asignar al nuevo entorno virtual es neutronclient, así que lo generaré dentro de mi directorio donde almaceno todos los entornos virtuales, ejecutando para ello sobre una nueva terminal el comando: alvaro@debian:~$ python3 -m venv /home/alvaro/virtualenv/neutronclient Una vez creado, tendremos que iniciarlo haciendo uso de source con el binario activate que se encuentra contenido en el directorio bin/: alvaro@debian:~$ source virtualenv/neutronclient/bin/activate Una vez activado, ya podremos proceder a instalar dicha herramienta haciendo uso de pip, no sin antes actualizar dicho paquete en sí mismo, haciendo para ello uso del comando: (neutronclient) alvaro@debian:~$ pip install --upgrade pip Cuando el gestor de paquetes haya sido actualizado, podremos instalar el paquete en cuestión de nombre neutron, ejecutando para ello el comando: (neutronclient) alvaro@debian:~$ pip install neutron Nuestro nuevo entorno virtual se encuentra ahora equipado para hacer uso de la herramienta neutron, así que vamos a generar un balanceador de carga asociado a la dirección IP virtual 10.0.0.200 y que como es lógico, pertenezca a la única subnet existente, con identificador 747952ea-a0a0-4bd5-8ad6-8dec0666a299. Para ello, haremos uso del comando: (neutronclient) alvaro@debian:~$ neutron lbaas-loadbalancer-create --vip-address 10.0.0.200 747952ea-a0a0-4bd5-8ad6-8dec0666a299 neutron CLI is deprecated and will be removed in the future. Use openstack CLI instead. Created a new loadbalancer: +---------------------+--------------------------------------+ | Field | Value | +---------------------+--------------------------------------+ | admin_state_up | True | | description | | | id | 155b3587-e06f-4909-b5a0-7f63d3040cd6 | | listeners | | | name | | | operating_status | OFFLINE | | pools | | | provider | haproxy | | provisioning_status | PENDING_CREATE | | tenant_id | dab5156c32654875b6c54ce23c6712a2 | | vip_address | 10.0.0.200 | | vip_port_id | 4581b50a-f2d4-4136-880a-bb2a34ecd730 | | vip_subnet_id | 747952ea-a0a0-4bd5-8ad6-8dec0666a299 | +---------------------+--------------------------------------+ La creación del balanceador de carga se ha iniciado, sin embargo, todavía no hemos asociado la IP flotante 172.22.200.28 con identificador aeffdcb1-1c4d-4471-b21e-fb606ceb30de a dicho balanceador con identificador 4581b50a-f2d4-4136-880a-bb2a34ecd730, así que procederemos a realizar dicha asociación ejecutando para ello el comando: (neutronclient) alvaro@debian:~$ neutron floatingip-associate aeffdcb1-1c4d-4471-b21e-fb606ceb30de 4581b50a-f2d4-4136-880a-bb2a34ecd730 neutron CLI is deprecated and will be removed in the future. Use openstack CLI instead. Associated floating IP aeffdcb1-1c4d-4471-b21e-fb606ceb30de La asociación de la IP flotante con el balanceador se ha completado correctamente, pudiendo acceder a partir de ahora desde nuestro navegador a la dirección 172.22.200.28 y pudiendo visualizar por tanto el contenido del servidor web alojado en la dirección 10.0.0.200. A pesar de que no es necesario ya que el único VirtualHost habilitado en el servidor web es el de PrestaShop, es recomendable crear una resolución estática en la máquina anfitriona para que así al acceder a www.example.com, resuelva dicha dirección a la IP flotante previamente generada. Para ello, haremos uso del comando: (ansible) alvaro@debian:~/GitHub/ansible-tfg$ sudo nano /etc/hosts 127.0.0.1 localhost 127.0.1.1 debian 172.22.200.28 www.example.com # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters Ha llegado el momento de la verdad, vamos a abrir el navegador de nuestra máquina anfitriona y vamos a tratar de acceder a www.example.com, obteniendo en mi caso el siguiente resultado: ¡Genial! El hecho de que el instalador de la aplicación se esté mostrando ya es una buena señal, aunque todavía queda una fase crítica: el proceso de instalación. Dicho proceso es totalmente trivial, así que vamos a obviarlo hasta llegar al punto de introducir la información de la base de datos. En mi caso, la información a introducir es la siguiente: Dirección del servidor de la base de datos: bd.example.com (también puede introducirse la dirección IP, pero ya que tenemos un registro DNS para ello, vamos a aprovecharlo) Nombre de la base de datos: prestashop Usuario de la base de datos: usuario Contraseña de la base de datos: usuario Tras ello, presionaremos el botón de ¡Comprobar la conexión con tu base de datos! para verificar que el acceso a la mismo se produce correctamente, obteniendo el siguiente resultado: Finalmente, pulsaremos en Siguiente y la instalación de nuestro CMS comenzará, mostrándonos en todo momento una barra de progreso de la siguiente forma: Transcurridos unos minutos, la instalación concluirá, en mi caso, sin ningún tipo de error. Además, se nos mostrarán las credenciales de acceso que previamente hemos introducido en la segunda fase de la instalación: Tal y como se muestra en la advertencia, por razones de seguridad es necesario eliminar el directorio install/, de manera que volveremos a nuestra terminal para proceder a ello. Actualmente nos encontramos con una sesión SSH abierta a la máquina ceph-admin, sin embargo, dicha máquina no cuenta con acceso al sistema de ficheros CephFS y por tanto, no podríamos llevar a cabo ninguna modificación. La única solución consiste en abrir una segunda conexión SSH a uno de los clientes desde dicha máquina, concretamente al usuario cephuser, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ssh cephuser@ceph-client1 Linux ceph-client1 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Posteriormente, ya podremos llevar a cabo la eliminación de dicho directorio, haciendo para ello uso del comando: cephuser@ceph-client1:~$ sudo rm -r /ceph/prestashop/install/ El subdirectorio install/ ha sido correctamente eliminado. De otro lado, existe una segunda práctica que es recomendable durante la instalación de un PrestaShop, consistente en modificar el nombre del directorio que contiene los ficheros de la página de administración, en este caso, del directorio admin/, para así evitar posibles ataques. En este caso, voy a renombrar el subdirectorio admin/ a admin966qmwvy7/, ejecutando para ello el comando: cephuser@ceph-client1:~$ sudo mv /ceph/prestashop/admin/ /ceph/prestashop/admin966qmwvy7 Antes de volver a nuestro navegador web, vamos a listar el contenido del directorio /ceph/prestashop/ para así verificar que los dos últimos comandos ejecutados han surtido efecto correctamente, haciendo para ello uso del comando: cephuser@ceph-client1:~$ ls -l /ceph/prestashop/ total 557 drwxr-xr-x 9 www-data www-data 28 Dec 2 2020 admin966qmwvy7 drwxr-xr-x 5 www-data www-data 6 Dec 2 2020 app -rw-r--r-- 1 www-data www-data 1316 Dec 2 2020 autoload.php drwxr-xr-x 2 www-data www-data 3 Dec 2 2020 bin drwxr-xr-x 8 www-data www-data 9 Dec 2 2020 cache drwxr-xr-x 25 www-data www-data 133 Dec 2 2020 classes -rw-r--r-- 1 www-data www-data 365681 Dec 2 2020 composer.lock drwxr-xr-x 5 www-data www-data 16 Jun 15 11:30 config drwxr-xr-x 4 www-data www-data 4 Dec 2 2020 controllers drwxr-xr-x 6 www-data www-data 7 Dec 2 2020 docs drwxr-xr-x 2 www-data www-data 2 Jun 15 11:28 download -rw-r--r-- 1 www-data www-data 2466 Dec 2 2020 error500.html -rw-r--r-- 1 www-data www-data 4830 Dec 2 2020 images.inc.php drwxr-xr-x 19 www-data www-data 35 Jun 15 11:28 img -rw-r--r-- 1 www-data www-data 1169 Dec 2 2020 index.php -rw-r--r-- 1 www-data www-data 1256 Dec 2 2020 init.php -rw-r--r-- 1 www-data www-data 5072 Dec 2 2020 INSTALL.txt drwxr-xr-x 7 www-data www-data 19 Dec 2 2020 js -rw-r--r-- 1 www-data www-data 186018 Dec 2 2020 LICENSES drwxr-xr-x 3 www-data www-data 97 Dec 2 2020 localization drwxr-xr-x 5 www-data www-data 5 Jun 15 11:36 mails drwxr-xr-x 74 www-data www-data 74 Jun 15 11:41 modules drwxr-xr-x 5 www-data www-data 5 Dec 2 2020 override drwxr-xr-x 2 www-data www-data 39 Dec 2 2020 pdf drwxr-xr-x 5 www-data www-data 4 Dec 2 2020 src drwxr-xr-x 4 www-data www-data 8 Dec 2 2020 themes drwxr-xr-x 3 www-data www-data 3 Dec 2 2020 tools drwxr-xr-x 4 www-data www-data 4 Jun 15 11:28 translations drwxr-xr-x 2 www-data www-data 2 Jun 15 11:28 upload drwxr-xr-x 5 www-data www-data 6 Dec 2 2020 var drwxr-xr-x 49 www-data www-data 49 Dec 2 2020 vendor drwxr-xr-x 2 www-data www-data 2 Dec 2 2020 webservice Como se puede apreciar, el directorio install/ ha sido correctamente eliminado y el directorio admin/ ha sido renombrado a admin966qmwvy7/, tal y como queríamos. La instalación de nuestro CMS ha finalizado, así que vamos a añadir un artículo de prueba que nos servirá para los tests que aplicaremos a continuación. Para ello, accederemos a la página de administración, ubicada en www.example.com/admin966qmwvy7, que nos debería mostrar lo siguiente: Una vez solicitadas e introducidas las credenciales indicadas durante la instalación de PrestaShop, accederemos a la página de administración, dentro de la cuál accederemos al apartado Catálogo en el menú izquierdo y seguidamente pulsaremos en Productos. Como es lógico, nos dirá que no tenemos ningún producto creado (a no ser que hayamos instalado los productos DEMO), así que seguidamente pulsaremos en Añade tu primer producto y rellenaremos los campos con la información que consideremos oportuna. En mi caso, el resultado final ha sido: Una vez introducida toda la información necesaria, pulsaremos en Guardar y volveremos una vez más al apartado Productos para verificar que la creación del mismo se ha completado correctamente, pudiendo apreciar lo siguiente: Efectivamente, mi tienda de PrestaShop ahora cuenta con un producto creado y cuya información se habrá guardado en la base de datos y en el sistema de ficheros distribuido. Por último, pulsaremos en Ver mi tienda en la parte superior derecha para así visualizar la tienda junto a su nuevo producto, quedando de la siguiente forma: Bien, una vez finalizada la creación del artículo en nuestra tienda, es hora de volver a la terminal para proseguir con una serie de pruebas cuya finalidad es demostrar el potencial de la alta disponibilidad, tanto por parte del clúster Ceph como del clúster Pacemaker. Lo primero que haremos será visualizar el estado del clúster Ceph, para así ver como se ha incrementado el número de objetos almacenados, ejecutando para ello el comando: cephuser@ceph-client1:~$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_OK services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 55m) mgr: ceph-mon1(active, since 55m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 2 standby osd: 3 osds: 3 up (since 53m), 3 in (since 59m) data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 38.36k objects, 723 MiB usage: 3.7 GiB used, 11 GiB / 15 GiB avail pgs: 81 active+clean Como se puede apreciar en la salida del comando ejecutado, hay un total de más de 38000 objetos en el sistema de ficheros distribuido, suponiendo un tamaño total de más de 700 MiB. Sin embargo, al existir redundancia, el espacio ocupado es superior, llegando a más de 3.7 GiB. Hasta ahora hemos visualizado en varias ocasiones el estado del clúster Ceph pero no hemos visto ninguna información relacionado al clúster Pacemaker, así que vamos a proceder a ello, haciendo uso del comando: cephuser@ceph-client1:~$ sudo pcs status Cluster name: mycluster Stack: corosync Current DC: ceph-client1 (version 2.0.1-9e909a5bdd) - partition with quorum Last updated: Tue Jun 15 11:58:02 2021 Last change: Tue Jun 15 11:25:06 2021 by root via cibadmin on ceph-client1 2 nodes configured 4 resources configured Online: [ ceph-client1 ceph-client2 ] Full list of resources: WebVirtualIP (ocf::heartbeat:IPaddr2): Started ceph-client1 WebSite (ocf::heartbeat:apache): Started ceph-client1 DBVirtualIP (ocf::heartbeat:IPaddr2): Started ceph-client1 Database (ocf::heartbeat:mysql): Started ceph-client1 Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: active/enabled Como era de esperar, los 2 nodos pertenecientes al clúster se encuentran actualmente activos (online) y el primero de ellos se encuentra ejecutando además los 4 recursos creados, es decir, en el momento que hemos accedido a www.example.com o bd.example.com, la máquina que ha procesado dichas peticiones ha sido ceph-client1. ¿Pero qué ocurriría si el primero de los nodos sufriese un fallo de hardware o incluso un apagón eléctrico? Vamos a comprobarlo, apagando para ello dicho nodo, ejecutando el comando: cephuser@ceph-client1:~$ sudo poweroff Connection to ceph-client1 closed by remote host. Connection to ceph-client1 closed. Una vez realizada la orden de apagado de la máquina, se cerrará la sesión SSH y volveremos a la máquina ceph-admin, estableciendo ahora una conexión SSH con el segundo de los nodos, para así realizar las comprobaciones oportunas. Para ello, haremos uso del comando: cephuser@ceph-admin:~/cephcluster$ ssh cephuser@ceph-client2 Linux ceph-client2 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Acto seguido vamos a proceder a visualizar de nuevo el estado del clúster de Pacemaker, ya que dicho clúster es el afectado en caso de fallo del nodo ceph-client1, pues para el clúster Ceph, dicho nodo es un simple cliente y no supone ninguna variación en su funcionamiento. Para verificar el estado del mismo, ejecutaremos el comando: cephuser@ceph-client2:~$ sudo pcs status Cluster name: mycluster Stack: corosync Current DC: ceph-client2 (version 2.0.1-9e909a5bdd) - partition with quorum Last updated: Tue Jun 15 11:59:54 2021 Last change: Tue Jun 15 11:59:43 2021 by hacluster via crmd on ceph-client2 2 nodes configured 4 resources configured Online: [ ceph-client2 ] OFFLINE: [ ceph-client1 ] Full list of resources: WebVirtualIP (ocf::heartbeat:IPaddr2): Started ceph-client2 WebSite (ocf::heartbeat:apache): Started ceph-client2 DBVirtualIP (ocf::heartbeat:IPaddr2): Started ceph-client2 Database (ocf::heartbeat:mysql): Started ceph-client2 Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: active/enabled Como se puede apreciar, ahora únicamente hay un nodo activo (ceph-client2), pues el otro ha pasado a estado offline como consecuencia del apagado. Además, podemos observar que los 4 recursos han sido migrados al segundo de los nodos, ya que el primero no se encuentra disponible para ello. Como consecuencia, las dos direcciones IP virtuales ahora están ubicadas en el nodo ceph-client2, así como el servidor web y el servidor de bases de datos. Nota: En raras ocasiones, Pacemaker no es capaz de levantar alguno de los recursos en el nuevo nodo tras un apagón dado que el nodo que ejecutaba dicho recurso previamente se ha quedado “colgado” y no ha terminado de morir, así que el recurso expira (timeout) y se para. Para solventarlo basta con ejecutar el comando pcs resource cleanup [RECURSO] y el recurso volverá a intentar levantarse. La teoría es muy bonita, pero todavía no he visto de forma práctica si nuestra aplicación sigue funcionando tal y como debería, así que vamos a volver a www.example.com y vamos a realizar una nueva petición, por ejemplo, intentando acceder al producto previamente generado: Efectivamente, la aplicación sigue funcionando y atendiendo las peticiones de los clientes, sin que estos hayan notado ningún tipo de diferencia, ya que lo único que ha ocurrido es que los recursos se han movido de nodo, sin embargo, la IP desde la que se accedía a los mismos no ha variado. Como es lógico, al únicamente existir dos nodos en el nodo Pacemaker, la caída del segundo de ellos sí provocaría una problema en el correcto funcionamiento de la aplicación, aunque siempre podemos aumentar el tamaño del clúster con nuevos nodos para así curarnos en salud y tener un mayor margen de error. Ya hemos comprobado el correcto funcionamiento de unos de los clúster, concretamente el que opera a un mayor nivel, pero todavía nos falta por verificar el funcionamiento del clúster Ceph, para verificar si nuestro almacenamiento realmente se encuentra replicado y tiene tolerancia a fallos. Para ello, vamos a salir de la máquina ceph-client2 y vamos a volver a ceph-admin (comando exit), desde la cuál vamos a apagar el primero de los OSDs, concretamente el nodo ceph-osd1, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ssh cephuser@ceph-osd1 "sudo poweroff" Una vez apagado el nodo, podremos verificar el estado del clúster Ceph para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_WARN 1 osds down 1 host (1 osds) down Degraded data redundancy: 23026/76760 objects degraded (29.997%), 49 pgs degraded, 49 pgs undersized services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 60m) mgr: ceph-mon1(active, since 60m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 1 standby osd: 3 osds: 2 up (since 112s), 3 in (since 64m) data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 38.38k objects, 722 MiB usage: 3.7 GiB used, 11 GiB / 15 GiB avail pgs: 23026/76760 objects degraded (29.997%) 49 active+undersized+degraded 32 active+clean Como era de esperar, los monitor nodes han detectado el problema ocurrido en el primero de los OSD, de manera que han decidido por quorum marcarlo como inactivo. En consecuencia a lo ocurrido, la redundancia de datos se encuentra degradada, aunque la información todavía es accesible, ya que si lo pensamos, tenemos un total de 2 copias de la información distribuidas entre 3 volúmenes, de manera que los otros 2 volúmenes todavía funcionales contienen la suficiente información como para poder acceder a los objetos almacenados en su totalidad. Para verificarlo, he vuelto al navegador web y he realizado una nueva petición en www.example.com, por ejemplo, intentando acceder al gestor de módulos de PrestaShop: Efectivamente, la aplicación sigue funcionando tal y como debería, aunque un fallo en uno de los dos OSDs restantes supondría una parada de la misma. Vamos a hacer la prueba apagando para ello el segundo OSD, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ssh cephuser@ceph-osd2 "sudo poweroff" Una vez apagado el nodo, podremos verificar el estado del clúster Ceph para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_WARN 1 filesystem is degraded insufficient standby MDS daemons available 1 MDSs report slow metadata IOs 2 osds down 2 hosts (2 osds) down Reduced data availability: 25 pgs stale Degraded data redundancy: 38380/76760 objects degraded (50.000%), 80 pgs degraded, 81 pgs undersized services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 62m) mgr: ceph-mon1(active, since 62m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up osd: 3 osds: 1 up (since 95s), 3 in (since 66m) data: volumes: 0/1 healthy, 1 recovering pools: 3 pools, 81 pgs objects: 38.38k objects, 722 MiB usage: 3.7 GiB used, 11 GiB / 15 GiB avail pgs: 38380/76760 objects degraded (50.000%) 55 active+undersized+degraded 25 stale+active+undersized+degraded 1 active+undersized Como era de esperar, los monitor nodes han vuelto a detectar el problema ocurrido en el segundo de los OSD, de manera que han decidido por quorum marcarlo como inactivo, habiendo ahora un total de 2 OSD inactivos. En consecuencia a lo ocurrido, la redundancia de datos se encuentra degradada, además de ser inaccesible la información, ya que si lo pensamos, tenemos un total de 2 copias de la información distribuidas entre 3 volúmenes, de manera que el único volumen funcional no contiene una copia al completo, imposibilitando por tanto el acceso a dicha información. Si en lugar de configurar el clúster para tener 2 copias lo hubiésemos configurado para que tuviese 3, la información todavía sería accesible. Antes de proceder con la última de las pruebas, quizás una de las más útiles, vamos a recuperar el estado funcional del clúster Ceph, levantando para ello las máquinas ceph-osd1 y ceph-osd2. Para ello, volveremos a nuestra terminal con el cliente de comandos de OpenStack y haremos uso de los siguientes comandos: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack server start ceph-osd1 (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack server start ceph-osd2 Para mayor tranquilidad, vamos a verificar que el clúster ha vuelto a estar totalmente funcional ejecutando para ello el siguiente comando desde el nodo ceph-admin: cephuser@ceph-admin:~/cephcluster$ ceph health HEALTH_OK Efectivamente, el clúster vuelve a estar ahora totalmente operativo y por tanto, nuestra aplicación vuelve a estar disponible. La última prueba consiste en simular un fallo en uno de los volúmenes empleados para nuestro clúster Ceph, algo que podría pasar en una situación cotidiana, y llevar a cabo un reemplazo del mismo, asegurando que la información es correctamente recuperada en el mismo. Una vez más, volveremos a nuestra terminal con el cliente de comandos de OpenStack y desasociaremos en caliente el disco del nodo ceph-osd3, simulando un fallo en el disco duro de uno de los nodos como si de una situación real se tratase, haciendo para ello uso del comando: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack server remove volume ceph-osd3 ceph-3 Una vez desasociado, vamos a proceder a generar un nuevo volumen de 5 GiB totalmente vacío de nombre ceph-recovery, que extrapolando al caso real, sería el nuevo disco duro que acabamos de comprar y hemos conectado a la máquina. Para ello, ejecutaremos el comando: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack volume create --size 5 ceph-recovery Cuando el volumen haya sido creado, únicamente faltará asociarlo a la máquina cuyo volumen previamente asociado ha fallado, haciendo para ello uso del comando: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack server add volume ceph-osd3 ceph-recovery Supuestamente, el volumen creado ya ha sido asociado, pero para verificarlo, vamos a listar todos los volúmenes existentes y sus asociaciones, para así comprobar además que el anterior volumen también ha sido desasociado. Para ello, ejecutaremos el comando: (openstackclient) alvaro@debian:~/GitHub/ansible-tfg$ openstack volume list +--------------------------------------+---------------+----------------+------+------------------------------------+ | ID | Name | Status | Size | Attached to | +--------------------------------------+---------------+----------------+------+------------------------------------+ | 69177591-bb69-45ca-b832-cdd9f29d48d0 | ceph-recovery | in-use | 5 | Attached to ceph-osd3 on /dev/vdb | | be00866a-367e-4eea-94b9-7f979b510a88 | ceph-3 | available | 5 | | | e85306e6-77a1-44a4-96df-febd62ecd99f | ceph-2 | in-use | 5 | Attached to ceph-osd2 on /dev/vdb | | c8035f39-38b9-482e-9c22-3a262ed9cbf2 | ceph-1 | in-use | 5 | Attached to ceph-osd1 on /dev/vdb | +--------------------------------------+---------------+----------------+------+------------------------------------+ Como se puede apreciar, el volumen de nombre ceph-3 ha sido correctamente desasociado y se encuentra disponible para su uso, mientras que de otro lado, el volumen ceph-recovery ha sido creado y asociado a la máquina ceph-osd3 sin ningún tipo de problema. Una vez cambiado el volumen, podremos verificar el estado del clúster Ceph para comprobar si ha ocurrido algún tipo de modificación en el mismo, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_WARN 1 osds down 1 host (1 osds) down Degraded data redundancy: 26952/76760 objects degraded (35.112%), 55 pgs degraded, 56 pgs undersized services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 67m) mgr: ceph-mon1(active, since 67m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 2 standby osd: 3 osds: 2 up (since 77s), 3 in (since 71m) data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 38.38k objects, 722 MiB usage: 2.7 GiB used, 12 GiB / 15 GiB avail pgs: 26952/76760 objects degraded (35.112%) 55 active+undersized+degraded 25 active+clean 1 active+undersized Como era de esperar, la redundancia de datos se encuentra degradada, ya que uno de los volúmenes ha “fallado”, y por consecuencia, el OSD se marca como inactivo, ya que si recordamos la explicación inicial, el demonio del OSD se encuentra asociado al volumen, no a la máquina en sí. Entrando un poco más en detalle, otra de las acciones que se han llevado a cabo en un segundo plano ha sido una redistribución gracias al algoritmo CRUSH para que así los clientes tengan una nueva ruta para encontrar los objetos, sin importar la pérdida de dicho volumen. Para llevar a cabo la sustitución del volumen en el clúster, lo primero que tendremos que hacer será sacar al anterior del mismo, necesitando para ello el identificador, que podremos obtener mediante la ejecución del comando: cephuser@ceph-admin:~/cephcluster$ ceph osd tree | grep -i down 2 hdd 0.00490 osd.2 down 1.00000 1.00000 En este caso, el identificador del OSD fallido es osd.2, de manera que lo eliminaremos del clúster haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph osd destroy osd.2 --yes-i-really-mean-it destroyed osd.2 Tal y como podemos apreciar en la salida del comando ejecutado, el OSD ha sido destruido, de manera que todo está listo para insertar el nuevo, no sin antes listar los discos asociados al nodo ceph-osd3, para así verificar que reconoce el nuevo volumen de 5 GiB. Para ello, ejecutaremos el comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy disk list ceph-osd3 ... [ceph-osd3][INFO ] Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors [ceph-osd3][INFO ] Disk /dev/vdc: 5 GiB, 5368709120 bytes, 10485760 sectors Efectivamente, así ha sido. La única diferencia es que en lugar de estar ubicado en /dev/vdb, lo está en /dev/vdc, pero no tiene mayor importancia, pues bastaría con cambiar dicha letra a la hora de darle formato al disco, haciendo para ello uso del comando: cephuser@ceph-admin:~/cephcluster$ ceph-deploy osd create --data /dev/vdc ceph-osd3 El volumen ha sido correctamente particionado y se debería encontrar listo para su uso, así que vamos a verificar el estado del clúster Ceph para comprobar si ha ocurrido algún tipo de modificación en el mismo, ejecutando para ello el comando: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_WARN Degraded data redundancy: 31830/76760 objects degraded (41.467%), 47 pgs degraded, 47 pgs undersized services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 99m) mgr: ceph-mon1(active, since 98m), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 2 standby osd: 4 osds: 3 up (since 29m), 3 in (since 22m); 48 remapped pgs data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 38.38k objects, 723 MiB usage: 1.8 GiB used, 13 GiB / 15 GiB avail pgs: 31830/76760 objects degraded (41.467%) 1347/76760 objects misplaced (1.755%) 40 active+recovery_wait+undersized+degraded+remapped 33 active+clean 6 active+undersized+degraded+remapped+backfill_wait 1 active+remapped+backfill_wait 1 active+recovering+undersized+degraded+remapped io: recovery: 180 KiB/s, 9 objects/s Como se puede apreciar en la parte inferior de la salida del comando ejecutado, el clúster ha comenzado un proceso de auto-reparación, pues se estará rellenando el nuevo volumen con la parte de información correspondiente, que en este caso duró hasta 2 horas. Una vez transcurrido un periodo de tiempo considerable, podremos volver a hacer uso del comando anterior para verificar si el proceso ha finalizado: cephuser@ceph-admin:~/cephcluster$ ceph status cluster: id: 5d47d41b-b223-4a16-bbac-688d8a61c359 health: HEALTH_WARN 1 daemons have recently crashed services: mon: 3 daemons, quorum ceph-mon2,ceph-mon1,ceph-mon3 (age 4h) mgr: ceph-mon1(active, since 4h), standbys: ceph-mon2, ceph-mon3 mds: 1/1 daemons up, 2 standby osd: 4 osds: 3 up (since 3h), 3 in (since 3h) data: volumes: 1/1 healthy pools: 3 pools, 81 pgs objects: 38.38k objects, 723 MiB usage: 2.7 GiB used, 12 GiB / 15 GiB avail pgs: 81 active+clean Efectivamente, tras un buen rato de espera, el proceso de auto-reparación ha finalizado y el disco se encuentra ahora totalmente funcional, tal y como se encontraba antes del cambio. Una vez más, vamos a hacer una prueba práctica para así demostrar que la teoría es correcta, realizando para ello una nueva petición en www.example.com, por ejemplo, intentando acceder al formulario de contacto de PrestaShop: Nuestra aplicación se encuentra actualmente operativa y preparada para tolerar prácticamente la mayoría de fallos tanto de hardware como de software, proporcionando una gran seguridad y margen de error. Espero que este artículo haya sido de gran ayuda además de interesante para todo el mundo. Si os habéis entretenido al menos la mitad de lo que lo he hecho yo, ¡me doy por satisfecho!]]></summary></entry><entry><title type="html">Despliegue de un cluster de Kubernetes</title><link href="https://www.alvarovf.com/hlc/2021/03/05/despliegue-cluster-kubernetes.html" rel="alternate" type="text/html" title="Despliegue de un cluster de Kubernetes" /><published>2021-03-05T09:10:00+00:00</published><updated>2021-03-05T09:10:00+00:00</updated><id>https://www.alvarovf.com/hlc/2021/03/05/despliegue-cluster-kubernetes</id><content type="html" xml:base="https://www.alvarovf.com/hlc/2021/03/05/despliegue-cluster-kubernetes.html"><![CDATA[<p>Los orquestadores de contenedores, como es el caso de <strong>Kubernetes</strong>, surgen dadas las claras limitaciones existentes en los contenedores, como por ejemplo la dificultad de cambiar entre versiones de una aplicación de una forma rápida, la dificultad de balancear la carga entre múltiples contenedores iguales, la necesidad de conectar contenedores que se ejecuten en diferentes demonios de Docker, la necesidad de actualizar una aplicación sin necesidad de dejar de ofrecer el servicio, la dificultad de mover la carga entre nodos…</p>

<p>Hasta hace varios años, existían tres alternativas para la orquestación, siendo todas ellas de <em>software</em> libre:</p>

<ul>
  <li><strong>Docker Swarm</strong></li>
  <li><strong>Apache Mesos</strong></li>
  <li><strong>Hashicorp Nomad</strong></li>
</ul>

<p>Sin embargo, hoy en día se acepta generalmente que el vencedor de dicha batalla ha sido <strong>Kubernetes</strong>, aunque el resto de ellos siguen activos como alternativas más sencillas a <strong>k8s</strong> (<em>Kubernetes</em>) o en su propio nicho.</p>

<p>Gracias a este proyecto, vamos a poder gestionar el despliegue de aplicaciones sobre contenedores, automatizando dicho despliegue y haciendo un gran énfasis en la escalabilidad, controlando en todo momento su ciclo de vida. Despliegue rápido de aplicaciones, escalabilidad de las aplicaciones al vuelo, integración de cambios sin interrupciones y posibilidad de limitar los recursos a utilizar son algunas de las características más interesantes de esta tecnología.</p>

<p>Es un proyecto totalmente extensible, gracias a la gran multitud de módulos y <em>plugins</em> que se ponen a nuestra total disposición y a través del cuál gestionaremos un <em>cluster</em> de nodos en los que podremos, como previamente he mencionado, desplegar aplicaciones sobre contenedores.</p>

<p>Dentro de nuestro <em>cluster</em> encontraremos una serie de componentes conocidos como <em>workers</em>, que son aquellas máquinas (virtuales o físicas) que ofrecerán la potencia de cómputo necesaria para desplegar nuestras aplicaciones. Todas ellas se encontrarán gestionadas por un nodo superior, conocido como <em>controller</em>. Cada uno de los nodos existentes tendrán una serie de características:</p>

<ul>
  <li><strong>Direcciones</strong>: <em>Hostname</em>, IP externa, IP interna…</li>
  <li><strong>Condición</strong>: Campo que describe el estado del nodo (listo, sin red, sin disco…).</li>
  <li><strong>Capacidad</strong>: Describe los recursos disponibles.</li>
  <li><strong>Info</strong>: Información general (<em>kernel</em>, <em>software</em> instalado, versiones…).</li>
</ul>

<p>Existen una serie de proyectos propios de <strong>k8s</strong> que nos permiten realizar una instalación de <strong>Kubernetes</strong> de una forma muy sencilla, como pueden ser <strong>Minikube</strong>, <strong>Kubeadm</strong> o <strong>k3s</strong>. En este caso, utilizaremos <strong>k3s</strong>, una distribución de <strong>Kubernetes</strong> muy ligera que incluye a su vez todas las herramientas necesarias para gestionar nuestro <em>cluster</em>.</p>

<p>Entre las herramientas que se nos proporcionan se incluye <strong>kubectl</strong>, una herramienta de línea de comandos desarrollada en Go para gestionar nuestros <em>clusters</em> de <strong>k8s</strong> de forma centralizada, permitiéndonos además, en caso de ser necesario, interactuar de forma directa con la API utilizando las credenciales del usuario.</p>

<p>Dado que <strong>Kubernetes</strong> es una tecnología que cambia de forma radical el concepto de despliegue de aplicaciones que teníamos hasta ahora, debemos conocer una serie de conceptos antes de empezar a trabajar con el mismo, ya que requiere un cambio de mentalidad para adaptarnos cuanto antes:</p>

<ul>
  <li>
    <p><strong>Pod</strong>: Un <em>pod</em> es la unidad más pequeña de <em>Kubernetes</em>, “equivalente” a un contenedor de <em>Docker</em>. Los <em>pods</em>, al igual que los contenedores de <em>Docker</em>, son efímeros, pues cuando se destruyen pierden toda la información que contenían. Si queremos que la información persista, debemos utilizar volúmenes.</p>
  </li>
  <li>
    <p><strong>ReplicaSet</strong>: Un <em>ReplicaSet</em> es un recurso de nivel superior que asegura que siempre se ejecute un número de réplicas de un <em>pod</em> determinado, asegurándonos, por tanto, que un conjunto de <em>pods</em> siempre estén funcionando y disponibles. Se podría resumir en que nos ofrece tolerancia a fallos y escalabilidad dinámica.</p>
  </li>
  <li>
    <p><strong>Deployment</strong>: Un <em>deployment</em> es la unidad de más alto nivel que podemos gestionar en <em>Kubernetes</em>, y nos ofrece control de réplicas, escalabilidad de <em>pods</em>, actualizaciones continuas, despliegues automáticos y <em>rollback</em> a versiones anteriores. A <em>grosso modo</em>, el <em>deployment</em> gestiona los <em>ReplicaSet</em> para que ofrezcan en todo momento el servicio con las características concretas que deseemos.</p>
  </li>
</ul>

<p>Para aclarar un poco los conceptos, vamos a apreciar de forma gráfica las diferencias entre el despliegue habitual de una aplicación frente el despliegue de una aplicación haciendo uso de <strong>Kubernetes</strong>:</p>

<ul>
  <li><strong>Despliegue habitual</strong>:</li>
</ul>

<p><img src="https://i.ibb.co/SwSBrwy/normal.jpg" alt="grafico1" title="Despliegue habitual" /></p>

<ul>
  <li><strong>Despliegue Kubernetes</strong>:</li>
</ul>

<p><img src="https://i.ibb.co/MsNF7Gm/k8s.jpg" alt="grafico2" title="Despliegue Kubernetes" /></p>

<p>Como se puede apreciar, en la primera imagen tenemos un conjunto de máquinas (<strong><em>Frontend</em></strong>) que están expuestas al exterior y la carga se balancea entre ellas a través de un balanceador de carga. Dichas máquinas interactuan con otras máquinas internas (<strong><em>Backend</em></strong>) para responder las peticiones entrantes.</p>

<p>De otro lado, en el despliegue de Kubernetes, tenemos un componente <strong>Ingress</strong> que es el que se expone al exterior (posteriormente hablamos del mismo), que es el encargado de enviar dichas peticiones al servicio <strong><em>Frontend</em></strong> (<strong><em>NodePort</em></strong>) para su correspondiente balanceo entre los <em>pods</em> existentes en el <em>ReplicaSet</em> del <em>deployment</em>, que como se puede suponer, el número de <em>pods</em> existente variará, en caso de así configurarlo, según la carga existente. Al ser una aplicación, tendrá que interactuar con los <em>pods</em> existentes en el otro <em>deployment</em> (normalmente bases de datos) para gestionar las peticiones, a través del servicio <strong><em>Backend</em></strong> (<strong><em>ClusterIP</em></strong>).</p>

<p>Si todavía no ha quedado claro del todo, no hay problema, ya que dentro de poco vamos a llevar a cabo un ejemplo en el que se entenderá todo a la perfección.</p>

<p>Antes de ello, y una vez entendida la parte teórica, es necesario realizar la configuración inicial de nuestro <em>cluster</em> de <strong>Kubernetes</strong>. Para ello, he generado un pequeño <a href="https://pastebin.com/wVnS6Dsp">escenario</a> compuesto por las siguientes máquinas:</p>

<ul>
  <li><strong>controller</strong>: Máquina conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.142</strong>. Actuará como <em>controller</em> y tiene 3 GB de RAM y 2 <em>cores</em> de CPU.</li>
  <li><strong>worker1</strong>: Máquina conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.143</strong>. Actuará como <em>worker</em> y tiene 3 GB de RAM y 2 <em>cores</em> de CPU.</li>
  <li><strong>worker2</strong>: Máquina conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.144</strong>. Actuará como <em>worker</em> y tiene 3 GB de RAM y 2 <em>cores</em> de CPU.</li>
</ul>

<p>Me encuentro actualmente haciendo uso de la máquina <strong>controller</strong>, de manera que lo primero que haremos será llevar a cabo la instalación del <em>software</em> necesario para gestionar nuestro <em>cluster</em>. Para ello, disponemos de dos opciones:</p>

<ul>
  <li>Utilizar el <em>script</em> de instalación, pensado para instalarlo como un servicio en máquinas que ejecuten <em>systemd</em>, pues como servicio que es, se configurará para estar en todo momento funcionando junto a la máquina anfitriona. Además de ello, se nos proporcionarán las herramientas de gestión entre las que se encuentra <em>kubectl</em>.</li>
  <li>Descargar la última <em>release</em> del <a href="https://github.com/k3s-io/k3s">repositorio</a> de GitHub y llevar a cabo la configuración de forma manual.</li>
</ul>

<p>Como se puede suponer, por comodidad y flexibilidad, la primera de las opciones es la elegida en este caso. Para llevar a cabo la instalación, tendremos que ejecutar el siguiente comando (en caso de no tenerlo, es necesario instalar <strong>curl</strong>):</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# curl <span class="nt">-sfL</span> https://get.k3s.io | sh -
<span class="o">[</span>INFO]  Finding release <span class="k">for </span>channel stable
<span class="o">[</span>INFO]  Using v1.20.4+k3s1 as release
<span class="o">[</span>INFO]  Downloading <span class="nb">hash </span>https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt
<span class="o">[</span>INFO]  Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s
<span class="o">[</span>INFO]  Verifying binary download
<span class="o">[</span>INFO]  Installing k3s to /usr/local/bin/k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/kubectl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/crictl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/ctr symlink to k3s
<span class="o">[</span>INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
<span class="o">[</span>INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh
<span class="o">[</span>INFO]  <span class="nb">env</span>: Creating environment file /etc/systemd/system/k3s.service.env
<span class="o">[</span>INFO]  systemd: Creating service file /etc/systemd/system/k3s.service
<span class="o">[</span>INFO]  systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
<span class="o">[</span>INFO]  systemd: Starting k3s</code></pre></figure>

<p>En consecuencia del proceso que está actualmente en ejecución, se habrá abierto un <em>socket TCP/IP</em> en el puerto por defecto que utiliza <strong>k3s</strong> (<strong>6443</strong>) que estará escuchando peticiones en todas las interfaces de la máquina, así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# netstat <span class="nt">-tlnp</span> | egrep <span class="s1">'6443'</span>
tcp6       0      0 :::6443                 :::<span class="k">*</span>                    LISTEN      445/k3s server</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-t</strong>: Filtramos únicamente para las conexiones que utilizan el protocolo TCP.</li>
  <li><strong>-l</strong>: Filtramos únicamente para los <em>sockets</em> que están actualmente escuchando peticiones (<strong>State = LISTEN</strong>).</li>
  <li><strong>-n</strong>: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos.</li>
  <li><strong>-p</strong>: Indicamos que muestre el PID y el nombre del proceso al que pertenece dicho <em>socket</em>.</li>
</ul>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería en el puerto <strong>6443/TCP</strong>, que será el utilizado para la posterior conexión con los nodos <em>worker</em>.</p>

<p>Como resultado de la instalación, algunos binarios se habrán instalado en el directorio <strong>/usr/local/bin/</strong> y estarán ahora disponibles para su uso. Para verificarlo, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# <span class="nb">ls</span> <span class="nt">-l</span> /usr/local/bin/
total 44296
lrwxrwxrwx 1 root root        3 Mar  4 07:40 crictl -&gt; k3s
lrwxrwxrwx 1 root root        3 Mar  4 07:40 ctr -&gt; k3s
<span class="nt">-rwxr-xr-x</span> 1 root root 45350912 Mar  4 07:40 k3s
<span class="nt">-rwxr-xr-x</span> 1 root root     1702 Mar  4 07:40 k3s-killall.sh
<span class="nt">-rwxr-xr-x</span> 1 root root     1037 Mar  4 07:40 k3s-uninstall.sh
lrwxrwxrwx 1 root root        3 Mar  4 07:40 kubectl -&gt; k3s</code></pre></figure>

<p>Como era de esperar, nuevos binarios han sido instalados para su correspondiente uso, entre los que se encuentra <strong>kubectl</strong>, que nos permitirá gestionar mediante línea de comandos y de forma centralizada, nuestro <em>cluster</em>. Para comprobar que está funcionando correctamente, vamos a listar todos los nodos existentes en el <em>cluster</em>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# kubectl get nodes
NAME         STATUS   ROLES                  AGE   VERSION
controller   Ready    control-plane,master   37s   v1.20.4+k3s1</code></pre></figure>

<p>Actualmente, existe un único nodo en el <em>cluster</em>, concretamente el controlador que es la máquina desde la que hemos ejecutado el comando. Sin embargo, esto no es lo que queremos, ya que tenemos otras dos máquinas que actuarán como <em>workers</em> y necesitamos integrarla al mismo.</p>

<p>Por motivos de seguridad, para vincular un nuevo nodo a un <em>cluster</em>, necesitamos hacer uso además de la dirección IP del <em>controller</em>, de un <em>token</em> único de verificación que podremos encontrar en el fichero de nombre <strong>/var/lib/rancher/k3s/server/node-token</strong>, de manera que visualizaremos su contenido haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# <span class="nb">cat</span> /var/lib/rancher/k3s/server/node-token
K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a</code></pre></figure>

<p>Todo está listo para llevar a cabo la instalación de <strong>k3s</strong> en los nodos <em>workers</em>, haciendo uso una vez más del método de instalación anterior. Sin embargo, durante la misma, tendremos que indicar de forma obligatoria dos parámetros, para así poder llevar a cabo la vinculación con el nodo <em>controller</em> y hacer que dichas máquinas, actúen, como se puede suponer, como <em>workers</em>:</p>

<ul>
  <li><strong>K3S_URL</strong>: Indicamos la URL de conexión al <em>controller</em>, que se generará siguiendo la sintaxis <strong>https://[IP]:6443</strong>. En este caso, la URL final sería <strong>https://192.168.1.142:6443</strong>.</li>
  <li><strong>K3S_TOKEN</strong>: Indicamos el <em>token</em> del nodo controlador previamente visualizado.</li>
</ul>

<p><strong>Nota</strong>: En caso de que los <em>hostname</em> de las máquinas <em>worker</em> coincidiesen, habría que diferenciarlas utilizando también el parámetro <strong>K3S_NODE_NAME</strong> durante la ejecución del <em>script</em> de instalación.</p>

<p>De esta manera, los comandos a ejecutar para instalar <strong>k3s</strong> en ambos nodos <em>worker</em> y vincularlos al <em>controller</em> serían (en caso de no tenerlo, es necesario instalar <strong>curl</strong>):</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@worker1:~# curl <span class="nt">-sfL</span> https://get.k3s.io | K3S<span class="se">\_</span><span class="nv">URL</span><span class="o">=</span>https://192.168.1.142:6443 K3S<span class="se">\_</span><span class="nv">TOKEN</span><span class="o">=</span>K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a sh -
<span class="o">[</span>INFO]  Finding release <span class="k">for </span>channel stable
<span class="o">[</span>INFO]  Using v1.20.4+k3s1 as release
<span class="o">[</span>INFO]  Downloading <span class="nb">hash </span>https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt
<span class="o">[</span>INFO]  Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s
<span class="o">[</span>INFO]  Verifying binary download
<span class="o">[</span>INFO]  Installing k3s to /usr/local/bin/k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/kubectl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/crictl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/ctr symlink to k3s
<span class="o">[</span>INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
<span class="o">[</span>INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
<span class="o">[</span>INFO]  <span class="nb">env</span>: Creating environment file /etc/systemd/system/k3s-agent.service.env
<span class="o">[</span>INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
<span class="o">[</span>INFO]  systemd: Enabling k3s-agent unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.
<span class="o">[</span>INFO]  systemd: Starting k3s-agent

root@worker2:~# curl <span class="nt">-sfL</span> https://get.k3s.io | K3S<span class="se">\_</span><span class="nv">URL</span><span class="o">=</span>https://192.168.1.142:6443 K3S<span class="se">\_</span><span class="nv">TOKEN</span><span class="o">=</span>K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a sh -
<span class="o">[</span>INFO]  Finding release <span class="k">for </span>channel stable
<span class="o">[</span>INFO]  Using v1.20.4+k3s1 as release
<span class="o">[</span>INFO]  Downloading <span class="nb">hash </span>https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt
<span class="o">[</span>INFO]  Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s
<span class="o">[</span>INFO]  Verifying binary download
<span class="o">[</span>INFO]  Installing k3s to /usr/local/bin/k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/kubectl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/crictl symlink to k3s
<span class="o">[</span>INFO]  Creating /usr/local/bin/ctr symlink to k3s
<span class="o">[</span>INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
<span class="o">[</span>INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
<span class="o">[</span>INFO]  <span class="nb">env</span>: Creating environment file /etc/systemd/system/k3s-agent.service.env
<span class="o">[</span>INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
<span class="o">[</span>INFO]  systemd: Enabling k3s-agent unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.
<span class="o">[</span>INFO]  systemd: Starting k3s-agent</code></pre></figure>

<p>Una vez finalizada la instalación de la distribución de <strong>Kubernetes</strong> en todos los nodos, volveremos al nodo controlador para verificar que la conexión con el resto de máquinas se ha llevado a cabo correctamente y ahora se encuentran a su disposición, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@controller:~# kubectl get nodes
NAME         STATUS   ROLES                  AGE    VERSION
controller   Ready    control-plane,master   12m    v1.20.4+k3s1
worker1      Ready    &lt;none&gt;                 101s   v1.20.4+k3s1
worker2      Ready    &lt;none&gt;                 16s    v1.20.4+k3s1</code></pre></figure>

<p>Como era de esperar, nuestro <em>cluster</em> de <em>Kubernetes</em> está ahora compuesto por un total de tres nodos: un <em>controller</em> y dos <em>worker</em>.</p>

<p>Sin embargo, si lo pensamos, gestionar nuestro <em>cluster</em> desde el nodo controlador puede llegar a ser un tanto incómodo, ya que tenemos que conectarnos al mismo cada vez que queramos llevar a cabo cualquier acción. La solución más sencilla a esto consiste en instalar <strong>kubectl</strong> en la máquina anfitriona, para así gestionar de forma remota el <em>cluster</em>.</p>

<p>Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de <em>Kubernetes</em>.</p>

<p>Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# <span class="nb">echo</span> <span class="s2">"deb https://apt.kubernetes.io/ kubernetes-xenial main"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/kubernetes.list</code></pre></figure>

<p>De otro lado, tendremos que añadir también la clave pública GPG de <em>Google</em> a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de Kubernetes que vamos a descargar, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# curl <span class="nt">-s</span> https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
OK</code></pre></figure>

<p>Tras ello, podremos dar paso a la instalación de <em>kubectl</em>, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>kubectl</code></pre></figure>

<p>El paquete <strong>kubectl</strong> ha sido instalado en la máquina, de manera que vamos a verificar la versión del mismo para así comprobar su correcto funcionamiento, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# kubectl version
Client Version: version.Info<span class="o">{</span>Major:<span class="s2">"1"</span>, Minor:<span class="s2">"20"</span>, GitVersion:<span class="s2">"v1.20.4"</span>, GitCommit:<span class="s2">"e87da0bd6e03ec3fea7933c4b5263d151aafd07c"</span>, GitTreeState:<span class="s2">"clean"</span>, BuildDate:<span class="s2">"2021-02-18T16:12:00Z"</span>, GoVersion:<span class="s2">"go1.15.8"</span>, Compiler:<span class="s2">"gc"</span>, Platform:<span class="s2">"linux/amd64"</span><span class="o">}</span>
The connection to the server localhost:8080 was refused - did you specify the right host or port?</code></pre></figure>

<p>Actualmente, nos encontramos haciendo uso de la versión <strong>v1.20.4</strong>, que es la misma que la instalada en los nodos del <em>cluster</em>, de manera que vamos a disponer de un soporte completo, al estar compartiendo la misma versión.</p>

<p>Si apreciamos la última línea, hemos obtenido un error de conexión rehusada. Es totalmente normal, ya que <em>kubectl</em> está tratando de acceder a nuestro <em>cluster</em> local, sin embargo, no existe ningún <em>cluster</em> en ejecución de forma local.</p>

<p>Para solucionarlo, necesitaremos un fichero <strong>config</strong> con las credenciales para el acceso al <em>cluster</em> remoto, que podremos encontrar en <strong>/etc/rancher/k3s/k3s.yaml</strong> en el nodo controlador, que debemos ubicar dentro de un directorio de nombre <strong>~/.kube</strong> en la máquina anfitriona, el cuál generaremos ejecutando el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# <span class="nb">mkdir</span> .kube</code></pre></figure>

<p>Una vez generado, podremos llevar a cabo la copia de dicho fichero desde el nodo controlador al directorio que acabamos de generar haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# scp root@192.168.1.142:/etc/rancher/k3s/k3s.yaml .kube/config</code></pre></figure>

<p>Una vez completada la transferencia, visualizaremos el contenido de dicho fichero mediante la ejecución del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# <span class="nb">cat</span> .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUyTVRRNE5ETTJNRFV3SGhjTk1qRXdNekEwTURjME1EQTFXaGNOTXpFd016QXlNRGMwTURBMQpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUyTVRRNE5ETTJNRFV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFTMXJNTU1ocWlGL0hDM1ZJWGtTNzlBQTRoZ1FYbkFMVkp3UVVMd1ArK0IKQXA5NFpwWFR0cDZTNnNDVFBxTCs5ZE5CcTBhYW9sSlBDWnN6UWNBWTFYdUJvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWMzNDhZOHErZzFMOEVxY1VpQTVNCmZCaEt6cDB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUs4RHp4T0p4M01jOVlkUHdaemhkU1h3Nnk3UlZtbnMKQ09qalExVGZXc2FSQWlFQW5TcUpEbVpnekNTSnZsUndXVVVEc0piQXZZTHBLdUJ3TytPM3QxVGhOR0k9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: <span class="o">{}</span>
<span class="nb">users</span>:
- name: default
  user:
    client-certificate-data: <span class="nv">LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNDQVRlZ0F3SUJBZ0lJZS84WU50UTZwd0l3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOakUwT0RRek5qQTFNQjRYRFRJeE1ETXdOREEzTkRBd05Wb1hEVEl5TURNdwpOREEzTkRBd05Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJNbzZDNTFwR3dBK2pEbGoKdFhYYUI2TkhoNnNmbXpUK2NkZnZZKzlzbGFBYms2Z1VjdFZvdUt4Q3JvRkFjeXN1b0UzM21HRlJkMEJqT2ViTwovZXh6YW42alNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUUhsZmJ3RTBEN1RlSHVDWnBlTkMvbTVRT3pHVEFLQmdncWhrak9QUVFEQWdOSkFEQkcKQWlFQWxTQ25ZRWJWbzVFYkdnZHpWcEhrbHZTdTNoSGdzSGRyOUgvOXJibmltUUFDSVFETVZCL056aGw4azF5ZApyNFZhQmY0OGJKenRSdWdUWjY2amxsdHB6c3orTEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZHpDQ0FSMmdBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwClpXNTBMV05oUURFMk1UUTRORE0yTURVd0hoY05NakV3TXpBME1EYzBNREExV2hjTk16RXdNekF5TURjME1EQTEKV2pBak1TRXdId1lEVlFRRERCaHJNM010WTJ4cFpXNTBMV05oUURFMk1UUTRORE0yTURVd1dUQVRCZ2NxaGtqTwpQUUlCQmdncWhrak9QUU1CQndOQ0FBUjgwMWVvY3JsQ09FNkZ1WmplQWxPVHFJTDhnM1l5bDltelI3UUdoQUlpCkFnNmovVkpRYzhXb2w1eHRWdUNBeGViODIvUDVSYlBkMHpzdnlVYlFxTk1ObzBJd1FEQU9CZ05WSFE4QkFmOEUKQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVCNVgyOEJOQSswM2g3Z21hWGpRdgo1dVVEc3hrd0NnWUlLb1pJemowRUF3SURTQUF3UlFJaEFNVkhMSmIyeDVuUzlMN2FUbEhYZ0V3QUp5U3F5UnNPClRTcDRoUGJoaG9NVkFpQUhlZEEzeEwxcTJ4aTNQaU9vQ2RiZnZzT2d2WCsrTE41QWNBUjYrNW1jNFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg</span><span class="o">==</span>
    client-key-data: <span class="nv">LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU1kd0RNc2ZsMlB0SjI4NDZlcTlVTlVwRk5HTVVwc3N1UmlkNnQ5dEZpUE5vQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFeWpvTG5Xa2JBRDZNT1dPMWRkb0hvMGVIcXgrYk5QNXgxKzlqNzJ5Vm9CdVRxQlJ5MVdpNApyRUt1Z1VCekt5NmdUZmVZWVZGM1FHTTU1czc5N0hOcWZnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo</span><span class="o">=</span></code></pre></figure>

<p>Como se puede apreciar, el mismo contiene algunas credenciales para el acceso remoto al <em>cluster</em>, que en mi caso, no me importa mostrar de forma pública ya que es un <em>cluster</em> de ejemplo y que será eliminado una vez finalizada la práctica.</p>

<p>Sin embargo, existe una directiva <strong>server</strong> que no se encuentra correctamente configurada, ya que está apuntando a la dirección de la máquina local (<strong>127.0.0.1</strong>), a pesar de que debería estar apuntando al nodo controlador (<strong>192.168.1.142</strong>). Para solucionarlo, modificaremos el fichero haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# nano .kube/config</code></pre></figure>

<p>El resultado final de dicha directiva, tras llevar a cabo la correspondiente modificación debería ser:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">server: https://192.168.1.142:6443</code></pre></figure>

<p>Toda la configuración en la máquina anfitriona para poder gestionar de forma remota el <em>cluster</em> ha finalizado, de manera que comprobaremos que tenemos acceso al mismo listando una vez más los nodos existentes en dicho <em>cluster</em>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# kubectl get nodes
NAME         STATUS   ROLES                  AGE   VERSION
controller   Ready    control-plane,master   30m   v1.20.4+k3s1
worker1      Ready    &lt;none&gt;                 19m   v1.20.4+k3s1
worker2      Ready    &lt;none&gt;                 18m   v1.20.4+k3s1</code></pre></figure>

<p>Efectivamente, así ha sido, de manera que podemos concluir que tenemos acceso remoto a nuestro <em>cluster</em> de <strong>Kubernetes</strong>.</p>

<p>Todo está listo para empezar la parte interesante del artículo, en la que vamos a realizar un despliegue de una aplicación conocida como “<strong>Let’s Chat</strong>”, utilizando para ello el repositorio de GitHub del IES Gonzalo Nazareno, que nos proporcionará todos los ficheros <strong>.yaml</strong> para definir los <em>deployment</em>, los servicios…</p>

<p>En mi caso, me he movido al directorio <strong>/home/alvaro/GitHub/</strong> de la máquina anfitriona haciendo uso de <code class="language-plaintext highlighter-rouge">cd</code>, pues será donde clonaré dicho repositorio, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub# git clone https://github.com/iesgn/kubernetes-storm.git
Clonando en <span class="s1">'kubernetes-storm'</span>...
remote: Enumerating objects: 288, <span class="k">done</span><span class="nb">.</span>
remote: Counting objects: 100% <span class="o">(</span>288/288<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Compressing objects: 100% <span class="o">(</span>213/213<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Total 288 <span class="o">(</span>delta 119<span class="o">)</span>, reused 224 <span class="o">(</span>delta 60<span class="o">)</span>, pack-reused 0
Recibiendo objetos: 100% <span class="se">\(</span>288/288<span class="o">)</span>, 6.36 MiB | 9.76 MiB/s, listo.
Resolviendo deltas: 100% <span class="o">(</span>119/119<span class="o">)</span>, listo.</code></pre></figure>

<p>Una vez finalizada la clonación, nos moveremos dentro del mismo, concretamente al directorio <strong>unidad3/ejemplos-3.2/ejemplo8/</strong>, pues es donde se encuentra la aplicación en cuestión, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub# <span class="nb">cd </span>kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8/</code></pre></figure>

<p>Para entrar en sintonía, vamos a listar el contenido del directorio actual, para así comprobar los distintos ficheros de los que disponemos, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">ls</span> <span class="nt">-l</span>
total 20
<span class="nt">-rw-r--r--</span> 1 root root 247 mar  4 09:12 ingress.yaml
<span class="nt">-rw-r--r--</span> 1 root root 394 mar  4 09:12 letschat-deployment.yaml
<span class="nt">-rw-r--r--</span> 1 root root 177 mar  4 09:12 letschat-srv.yaml
<span class="nt">-rw-r--r--</span> 1 root root 358 mar  4 09:12 mongo-deployment.yaml
<span class="nt">-rw-r--r--</span> 1 root root 149 mar  4 09:12 mongo-srv.yaml</code></pre></figure>

<p>Como se puede apreciar, tenemos un total de <strong>5</strong> ficheros, los cuales iremos explicando con mayor detenimiento durante el transcurso de la tarea. El primero en el que debemos fijarnos es <strong>mongo-deployment.yaml</strong>, de manera que visualizaremos el contenido del mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">cat </span>mongo-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongo
  labels:
    name: mongo
spec:
  replicas: 1
  selector:
    matchLabels:
      name: mongo
  template:
    metadata:
      labels:
        name: mongo
    spec:
      containers:
      - name: mongo
        image: mongo
        ports:
          - name: mongo
            containerPort: 27017</code></pre></figure>

<p>Sin entrar en demasiado detalle, dentro del mismo hemos definido un <em>deployment</em> que generará por consecuencia un <em>ReplicaSet</em> con un único <em>pod</em>, por ahora, que ejecutará una imagen <strong>mongo</strong> que ofrecerá su servicio en el puerto <strong>27017</strong>. Gracias al mismo, estamos definiendo la parte <strong>Backend</strong> de nuestra aplicación, con la que posteriormente interactuará el <strong>Frontend</strong> para ofrecer el servicio.</p>

<p>Todo está listo para definir el <em>deployment</em> a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply <span class="nt">-f</span> mongo-deployment.yaml 
deployment.apps/mongo created</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, el <em>deployment</em> ha sido correctamente definido, de manera que vamos a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES   SELECTOR
deployment.apps/mongo   1/1     1            1           51s   mongo        mongo    <span class="nv">name</span><span class="o">=</span>mongo

NAME                               DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES   SELECTOR
replicaset.apps/mongo-5c694c878b   1         1         1       50s   mongo        mongo    <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b

NAME                         READY   STATUS    RESTARTS   AGE   IP          NODE      NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-46ts2   1/1     Running   0          50s   10.42.2.4   worker2   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Tal y como he mencionado previamente, el <em>deployment</em> que he definido ha generado a su vez un <em>ReplicaSet</em> asociado que contiene un único <em>pod</em> con la imagen de <strong>mongo</strong>, que como se puede apreciar, se está ejecutando sobre el nodo <strong>worker2</strong>.</p>

<p>Para que empecemos a comprender el potencial de <strong>Kubernetes</strong>, vamos a tratar de eliminar el <em>pod</em> asociado al <em>ReplicaSet</em> que se ha generado, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl delete pod/mongo-5c694c878b-46ts2
pod <span class="s2">"mongo-5c694c878b-46ts2"</span> deleted</code></pre></figure>

<p>En un principio, basándonos en la salida del comando ejecutado, podríamos concluir que el <em>pod</em> ha sido eliminado y que por tanto, se ha dejado de ofrecer el servicio deseado. Vamos a volver a listar los objetos existentes en nuestro <em>cluster</em>, ejecutando para ello el comando utilizado con anterioridad:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES   SELECTOR
deployment.apps/mongo      1/1     1            1           3m33s   mongo        mongo    <span class="nv">name</span><span class="o">=</span>mongo

NAME                                  DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES   SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         1       3m32s   mongo        mongo    <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b

NAME                            READY   STATUS    RESTARTS   AGE   IP          NODE      NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Running   0          13s   10.42.2.5   worker2   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>¿Magia? Podríamos decir que sí. <strong>Kubernetes</strong>, no conforme con nuestra decisión de eliminar el <em>pod</em> asociado, ha generado un nuevo <em>pod</em> de forma inmediata que de nuevo se está ejecutando en el nodo <strong>worker2</strong> (podría haberse ejecutado en el nodo <strong>worker1</strong>, pero ha dado la casualidad), de manera que nuestro servicio vuelve a estar operativo de forma casi inmediata.</p>

<p>Existe un concepto que todavía no hemos introducido, y que nos será necesario para continuar, los <strong>servicios</strong>.</p>

<p>Los <strong>servicios</strong> (<em>services</em>) nos permiten acceder a nuestras aplicaciones, ya que proporcionan una capa de abstracción que define un conjunto de <em>pods</em> que implementan un micro-servicio. Nos ofrecen una dirección IP virtual y un nombre que identifica al conjunto de <em>pods</em> que representa, al cuál nos podremos conectar. Dependiendo del tipo de servicio, <strong>NodePort</strong> o <strong>ClusterIP</strong>, permitiremos el acceso desde el exterior desde un puerto aleatorio del <em>controller</em> o únicamente de forma interna desde otros <em>pods</em>, respectivamente.</p>

<p>Una vez comprendido el concepto <strong>servicio</strong>, podremos proceder a definir el contenido del fichero <strong>mongo-srv.yaml</strong>, que como ya hemos mencionado, proporcionará una dirección IP virtual para acceder de una forma totalmente abstracta y ajena a nosotros a los <em>pods</em> existentes en el <em>ReplicaSet</em>, pues se encargará de “balancear” la carga y enviar las peticiones únicamente a aquellos <em>pods</em> que se encuentren activos, para asegurar que el servicio funcione en todo momento, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">cat </span>mongo-srv.yaml 
apiVersion: v1
kind: Service
metadata:
  name: mongo
spec:
  ports:
  - name: mongo
    port: 27017
    targetPort: mongo
  selector:
    name: mongo</code></pre></figure>

<p>Sin entrar en demasiado detalle, dentro del mismo hemos definido un <em>service</em> de tipo <strong>ClusterIP</strong>, es decir, que únicamente será accesible de forma interna por el resto de <em>pods</em> (valor por defecto) y ofrecerá su servicio en el puerto <strong>27017</strong> afectando a aquellas máquinas que están ejecutando <strong>mongo</strong>.</p>

<p>Todo está listo para definir el <em>service</em> a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply <span class="nt">-f</span> mongo-srv.yaml 
service/mongo created</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, el <em>service</em> ha sido correctamente definido, de manera que vamos a listar todos los servicios existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT<span class="o">(</span>S<span class="o">)</span>     AGE
kubernetes   ClusterIP   10.43.0.1     &lt;none&gt;        443/TCP     50m
mongo        ClusterIP   10.43.10.91   &lt;none&gt;        27017/TCP   20s</code></pre></figure>

<p>Efectivamente, se ha generado un nuevo servicio de nombre <strong>mongo</strong> únicamente accesible a través de la dirección del Cluster IP para los <em>pods</em> internos, pues se trata de un servicio crítico y debemos priorizar en todo momento su seguridad.</p>

<p>Ha llegado la hora de desplegar la aplicación <strong>Let’s Chat</strong> como tal, <em>deployment</em> que se encontrará definido en el fichero <strong>letschat-deployment.yaml</strong>, de manera que visualizaremos el contenido del mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">cat </span>letschat-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: letschat
  labels:
    name: letschat
spec:
  replicas: 1 
  selector:
    matchLabels:
      name: letschat
  template:
    metadata:
      labels:
        name: letschat
    spec:
      containers:
      - name: letschat
        image: sdelements/lets-chat
        ports:
          - name: http-server
            containerPort: 8080</code></pre></figure>

<p>Sin entrar en demasiado detalle, dentro del mismo hemos definido un <em>deployment</em> que generará por consecuencia un <em>ReplicaSet</em> con un único <em>pod</em>, por ahora, que ejecutará una imagen <strong>sdelements/lets-chat</strong> que ofrecerá su servicio en el puerto <strong>8080</strong>. Gracias al mismo, estamos definiendo la parte <strong>Frontend</strong> de nuestra aplicación, que interactuará con la parte <strong>Backend</strong> previamente definida para ofrecer el servicio.</p>

<p>Todo está listo para definir el <em>deployment</em> a partir de dicho fichero. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply <span class="nt">-f</span> letschat-deployment.yaml 
deployment.apps/letschat created</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, el <em>deployment</em> ha sido correctamente definido, de manera que vamos a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                 SELECTOR
deployment.apps/mongo      1/1     1            1           2m46s   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo
deployment.apps/letschat   1/1     1            1           8s      letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat

NAME                                  DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES                 SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         1       2m45s   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b
replicaset.apps/letschat-7c66bd64f5   1         1         1       8s      letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat,pod-template-hash<span class="o">=</span>7c66bd64f5

NAME                            READY   STATUS    RESTARTS   AGE     IP          NODE      NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Running   0          2m45s   10.42.2.5   worker2   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-6mt2m   1/1     Running   0          8s      10.42.1.5   worker1   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Tal y como he mencionado previamente, el <em>deployment</em> que he definido ha generado a su vez un <em>ReplicaSet</em> asociado que contiene un único <em>pod</em> con la imagen de <strong>sdelements/lets-chat</strong>, que como se puede apreciar, se está ejecutando sobre el nodo <strong>worker1</strong>.</p>

<p>Una vez definida nuestra aplicación, podremos desplegar el servicio existente en el fichero <strong>letschat-srv.yaml</strong>, exponiendo así la parte <strong>Frontend</strong> de nuestra aplicación al exterior para así hacerla accesible, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">cat </span>letschat-srv.yaml 
apiVersion: v1
kind: Service
metadata:
  name: letschat
spec:
  <span class="nb">type</span>: NodePort
  ports:
  - name: http
    port: 8080
    targetPort: http-server
  selector:
    name: letschat</code></pre></figure>

<p>Sin entrar en demasiado detalle, dentro del mismo hemos definido un <em>service</em> de tipo <strong>NodePort</strong>, es decir, que será accesible de forma externa y redirigirá las peticiones al puerto <strong>8080</strong> de aquellas máquinas que están ejecutando <strong>letschat</strong>.</p>

<p>Todo está listo para definir el <em>service</em> a partir de dicho fichero. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply <span class="nt">-f</span> letschat-srv.yaml 
service/letschat created</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, el <em>service</em> ha sido correctamente definido, de manera que vamos a listar todos los servicios existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT<span class="o">(</span>S<span class="o">)</span>          AGE
kubernetes   ClusterIP   10.43.0.1      &lt;none&gt;        443/TCP          53m
mongo        ClusterIP   10.43.10.91    &lt;none&gt;        27017/TCP        3m25s
letschat     NodePort    10.43.78.152   &lt;none&gt;        8080:32468/TCP   16s</code></pre></figure>

<p>Efectivamente, se ha generado un nuevo servicio de nombre <strong>letschat</strong> accesible desde el exterior a través del puerto <strong>32468</strong> de la máquina controladora. Sin embargo, esto no es para nada práctico, pues no es viable hacer a los clientes conectarse de forma manual al puerto 32468 en cada conexión, poniendo al final de la URL ‘<strong>:32468</strong>’. Para solucionarlo, haremos uso de los <strong><em>Ingress controller</em></strong>.</p>

<p>Los controladores <em>Ingress</em> o <em>Ingress controller</em> nos permiten utilizar un <em>proxy</em> inverso que por medio de reglas de encaminamiento que obtiene de la API de <em>Kubernetes</em> nos permite el acceso a nuestras aplicaciones por medio de nombres.</p>

<p><img src="https://i.ibb.co/6Rk1jGq/ingress.jpg" alt="grafico3" title="Ingress controller" /></p>

<p>Una vez comprendido el concepto <strong><em>ingress</em></strong>, podremos proceder a definir el contenido del fichero <strong>ingress.yaml</strong>, que como ya hemos mencionado, proporcionará un <em>proxy</em> inverso para acceder a través de un nombre a la aplicación que hemos desplegado, proporcionando por tanto una capa de abstracción más, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# <span class="nb">cat </span>ingress.yaml 
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-letschat
spec:
  rules:
  - host: www.letschat.com
    http:
      paths:
      - path: <span class="s2">"/"</span>
        backend:
          serviceName: letschat
          servicePort: 8080</code></pre></figure>

<p>En este caso, la versión de la API que utiliza el fichero se encuentra casi obsoleta, de manera que he decidido actualizar el contenido para adaptarlo, siguiendo la <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">documentación</a>, a la versión <strong>v1</strong>. Para modificar el fichero ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# nano ingress.yaml</code></pre></figure>

<p>El resultado final del fichero, tras llevar a cabo las correspondientes modificaciones sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-letschat
spec:
  rules:
  - host: www.letschat.com
    http:
      paths:
      - path: <span class="s2">"/"</span>
        pathType: Prefix
        backend:
          service:
            name: letschat
            port:
              number: 8080</code></pre></figure>

<p>Sin entrar en demasiado detalle, dentro del mismo hemos definido un <em>ingress</em> que será accesible a través del nombre <strong>www.letschat.com</strong> afectando a aquellas máquinas que están ejecutando <strong>letschat</strong>, en el puerto <strong>8080</strong>.</p>

<p>Todo está listo para definir el <em>ingress</em> a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply <span class="nt">-f</span> ingress.yaml 
ingress.networking.k8s.io/ingress-letschat created</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, el <em>ingress</em> ha sido correctamente definido, de manera que vamos a listar todos los <em>ingress</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get ingress
NAME               CLASS    HOSTS              ADDRESS         PORTS   AGE
ingress-letschat   &lt;none&gt;   www.letschat.com   192.168.1.142   80      70s</code></pre></figure>

<p>Efectivamente, se ha generado un nuevo <em>ingress</em> de nombre <strong>ingress-letschat</strong> accesible a través del nombre <strong>www.letschat.com</strong> que deberá apuntar a la dirección IP del nodo controlador <strong>192.168.1.142</strong>.</p>

<p>Para hacer que el nombre <strong>www.letschat.com</strong> se resuelva a la dirección IP deseada, modificaremos el fichero de resolución estática de nombres <strong>/etc/hosts</strong>, indicando una nueva entrada para el mismo. Para modificar dicho fichero, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# nano /etc/hosts</code></pre></figure>

<p>El resultado del mismo, tras añadir la nueva correspondencia entre nombre y dirección IP sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">127.0.0.1       localhost
127.0.1.1       debian
192.168.1.142   www.letschat.com

<span class="c"># The following lines are desirable for IPv6 capable hosts
</span>
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters</code></pre></figure>

<p>Todo está listo para tratar de acceder a <strong>http://www.letschat.com</strong> a través de nuestro navegador, pudiendo apreciar lo siguiente</p>

<p><img src="https://i.ibb.co/64M0x0T/Captura-de-pantalla-de-2021-03-04-09-51-43.png" alt="letschat1" title="Let's Chat" /></p>

<p>Como era de esperar, la aplicación desplegada en <strong>Kubernetes</strong> se encuentra totalmente operativa y podemos acceder a la misma a través de un nombre de dominio.</p>

<p>Sin embargo, esto no es todo, ya que como bien sabemos, <em>Kubernetes</em> nos ofrece una flexibilidad impresionante sobre el <em>cluster</em> existente, como por ejemplo escalar el número de <em>pods</em> existente en el <em>deployment</em> referente a la aplicación <strong>Let’s Chat</strong> para así poder atender un mayor número de peticiones de forma concurrente. En este caso, vamos a escalar a un total de 10 <em>pods</em>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl scale deploy letschat <span class="nt">--replicas</span><span class="o">=</span>10
deployment.apps/letschat scaled</code></pre></figure>

<p>Una vez escalada la aplicación, volveremos a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                 SELECTOR
deployment.apps/mongo      1/1     1            1           34m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo
deployment.apps/letschat   10/10   10           10          31m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat

NAME                                  DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                 SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         1       34m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b
replicaset.apps/letschat-7c66bd64f5   10        10        10      31m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat,pod-template-hash<span class="o">=</span>7c66bd64f5

NAME                            READY   STATUS    RESTARTS   AGE     IP           NODE         NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Running   0          30m     10.42.2.5    worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wg4cs   1/1     Running   0          8m53s   10.42.0.8    controller   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-d4ccg   1/1     Running   0          11s     10.42.1.28   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-x2s95   1/1     Running   0          11s     10.42.2.25   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-68fgp   1/1     Running   0          11s     10.42.2.23   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-2mlbn   1/1     Running   0          11s     10.42.2.24   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-gfsf5   1/1     Running   0          11s     10.42.2.26   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wtzhq   1/1     Running   0          11s     10.42.1.29   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-pvskq   1/1     Running   0          11s     10.42.1.30   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-fqjrz   1/1     Running   0          11s     10.42.1.27   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-h2zbg   1/1     Running   0          11s     10.42.1.31   worker1      &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Como se puede apreciar, el número de <em>pods</em> referentes al <em>ReplicaSet</em> <strong>letschat</strong> ha aumentado de <strong>1</strong> a <strong>10</strong>, repartiéndose los mismos entre los dos nodos <em>worker</em> principalmente, aunque cuando aumentamos muy considerablemente la carga en los mismos, puede ocurrir dependiendo de la distribución de <strong>k8s</strong> que algunos <em>pods</em> se ejecuten en el nodo controlador para así desahogarlos un poco. En consecuencia, nuestra aplicación puede soportar ahora una mayor cantidad de tráfico.</p>

<p>Por último, vamos a simular una situación real en la que uno de los nodos <em>worker</em> cayese, para así apreciar como <strong>k8s</strong> es capaz de gestionar la situación levantando ahora todos los <em>pods</em> entre el <em>worker</em> y el <em>controller</em> para así no dejar de ofrecer el servicio en ningún momento.</p>

<p>En mi caso, al estar haciendo uso de un escenario en <strong>vagrant</strong>, bastaría con ejecutar la siguiente instrucción para apagar el nodo <strong>worker2</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~/vagrant/kubernetes<span class="nv">$ </span>vagrant halt worker2</code></pre></figure>

<p>Si tras ello volvemos a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, podremos apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                 SELECTOR
deployment.apps/mongo      0/1     1            0           39m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo
deployment.apps/letschat   1/10    10           1           37m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat

NAME                                  DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                 SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         0       39m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b
replicaset.apps/letschat-7c66bd64f5   10        10        1       37m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat,pod-template-hash<span class="o">=</span>7c66bd64f5

NAME                            READY   STATUS             RESTARTS   AGE     IP           NODE         NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Running            0          36m     10.42.2.5    worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-x2s95   1/1     Running            0          5m38s   10.42.2.25   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-gfsf5   1/1     Running            0          5m38s   10.42.2.26   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-68fgp   1/1     Running            0          5m38s   10.42.2.23   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wtzhq   0/1     CrashLoopBackOff   4          5m38s   10.42.1.29   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-pvskq   0/1     CrashLoopBackOff   4          5m38s   10.42.1.30   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-h2zbg   0/1     CrashLoopBackOff   4          5m38s   10.42.1.31   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-d4ccg   0/1     CrashLoopBackOff   4          5m38s   10.42.1.28   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-fqjrz   0/1     CrashLoopBackOff   4          5m38s   10.42.1.27   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wg4cs   0/1     CrashLoopBackOff   4          14m     10.42.0.8    controller   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-w75xr   1/1     Running            5          4m57s   10.42.0.12   controller   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Como podemos apreciar, los <em>pods</em> en ejecución en el nodo <strong>worker1</strong> están empezando a fallar, ya que no tienen conexión con el <strong>Backend</strong> que estaba ubicado en el nodo <strong>worker2</strong> que ha caído. Esto es importante tenerlo en cuenta, ya que el <em>backend</em> también debe estar configurado en alta disponibilidad para evitar puntos de fallo únicos como en este caso.</p>

<p>Por defecto, existe un parámetro de nombre <strong>pod-eviction-timeout</strong> que especifica el tiempo que ha de transcurrir hasta que los <em>pods</em> se muevan a otro nodo <em>worker</em> en caso de dejar de funcionar aquel en el que se estaban ejecutando, cuyo valor es de <strong>5 minutos</strong>. Dependiendo de nuestras circunstancias, tendremos que modificar dicho valor para adaptarlo a nuestras necesidades, intentando que no sea muy pequeño ni tampoco muy grande.</p>

<p>Tras haber pasado alrededor de 5 minutos, podremos volver a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, pudiendo apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                 SELECTOR
deployment.apps/mongo      1/1     1            1           44m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo
deployment.apps/letschat   10/10   10           10          41m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat

NAME                                  DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                 SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         1       44m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b
replicaset.apps/letschat-7c66bd64f5   10        10        10      41m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat,pod-template-hash<span class="o">=</span>7c66bd64f5

NAME                            READY   STATUS        RESTARTS   AGE     IP           NODE         NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Terminating   0          41m     10.42.2.5    worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-gfsf5   1/1     Terminating   0          10m     10.42.2.26   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-x2s95   1/1     Terminating   0          10m     10.42.2.25   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-68fgp   1/1     Terminating   0          10m     10.42.2.23   worker2      &lt;none&gt;           &lt;none&gt;
pod/mongo-5c694c878b-rnnr2      1/1     Running       0          2m51s   10.42.1.33   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-9s8dw   1/1     Running       2          2m51s   10.42.1.32   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-ljcw6   1/1     Running       2          2m51s   10.42.0.14   controller   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-tdcpq   1/1     Running       2          2m51s   10.42.0.13   controller   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-w75xr   1/1     Running       6          9m47s   10.42.0.12   controller   &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wtzhq   1/1     Running       6          10m     10.42.1.29   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-pvskq   1/1     Running       6          10m     10.42.1.30   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-d4ccg   1/1     Running       6          10m     10.42.1.28   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-h2zbg   1/1     Running       6          10m     10.42.1.31   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-fqjrz   1/1     Running       6          10m     10.42.1.27   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-wg4cs   1/1     Running       6          19m     10.42.0.8    controller   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Como se puede apreciar, el número de <em>pods</em> referentes al <em>ReplicaSet</em> <strong>letschat</strong> sigue siendo <strong>10</strong>, repartiéndose los mismos entre el primer nodo <em>worker</em> y el nodo controlador para así desahogarlo un poco. En consecuencia, nuestra aplicación sigue estando totalmente operativa, tal y como podemos ver:</p>

<p><img src="https://i.ibb.co/64M0x0T/Captura-de-pantalla-de-2021-03-04-09-51-43.png" alt="letschat1" title="Let's Chat" /></p>

<p>Al igual que podemos escalar nuestro <em>deployment</em> para que tenga un mayor número de <em>pods</em>, también podemos desescalarlo para eliminar <em>pods</em> en caso de que el tráfico se haya visto reducido (generalmente esta tarea se suele delegar a un componente para hacerlo de forma automatizada). En este caso, vamos a desescalar a un total de 1 <em>pod</em>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl scale deploy letschat <span class="nt">--replicas</span><span class="o">=</span>1
deployment.apps/letschat scaled</code></pre></figure>

<p>Una vez desescalada la aplicación, volveremos a listar todos los <em>deployment</em>, <em>ReplicaSet</em> y <em>pods</em> existentes en nuestro <em>cluster</em> de <strong>Kubernetes</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po <span class="nt">-o</span> wide
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                 SELECTOR
deployment.apps/mongo      1/1     1            1           45m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo
deployment.apps/letschat   1/1     1            1           43m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat

NAME                                  DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                 SELECTOR
replicaset.apps/mongo-5c694c878b      1         1         1       45m   mongo        mongo                  <span class="nv">name</span><span class="o">=</span>mongo,pod-template-hash<span class="o">=</span>5c694c878b
replicaset.apps/letschat-7c66bd64f5   1         1         1       43m   letschat     sdelements/lets-chat   <span class="nv">name</span><span class="o">=</span>letschat,pod-template-hash<span class="o">=</span>7c66bd64f5

NAME                            READY   STATUS        RESTARTS   AGE    IP           NODE         NOMINATED NODE   READINESS GATES
pod/mongo-5c694c878b-5449c      1/1     Terminating   0          42m    10.42.2.5    worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-gfsf5   1/1     Terminating   0          11m    10.42.2.26   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-x2s95   1/1     Terminating   0          11m    10.42.2.25   worker2      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-68fgp   1/1     Terminating   0          11m    10.42.2.23   worker2      &lt;none&gt;           &lt;none&gt;
pod/mongo-5c694c878b-rnnr2      1/1     Running       0          4m5s   10.42.1.33   worker1      &lt;none&gt;           &lt;none&gt;
pod/letschat-7c66bd64f5-ljcw6   1/1     Running       2          4m5s   10.42.0.14   controller   &lt;none&gt;           &lt;none&gt;</code></pre></figure>

<p>Como se puede apreciar, el número de <em>pods</em> referentes al <em>ReplicaSet</em> <strong>letschat</strong> ha desminuido de <strong>10</strong> a <strong>1</strong>. Los otros 4 <em>pods</em> son referentes al nodo <strong>worker2</strong> que todavía no han podido eliminarse ya que la máquina se encuentra inactiva.</p>

<p>Espero que el artículo haya contribuido a la comprensión de esta magnífica tecnología que va a ayudar a que el despliegue de las aplicaciones sea una tarea menos tediosa y podamos gestionar en todo momento de una forma muy sencilla, entre otras cosas, los despliegues y <em>rollbacks</em> entre versiones de la aplicación.</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="hlc" /><summary type="html"><![CDATA[Los orquestadores de contenedores, como es el caso de Kubernetes, surgen dadas las claras limitaciones existentes en los contenedores, como por ejemplo la dificultad de cambiar entre versiones de una aplicación de una forma rápida, la dificultad de balancear la carga entre múltiples contenedores iguales, la necesidad de conectar contenedores que se ejecuten en diferentes demonios de Docker, la necesidad de actualizar una aplicación sin necesidad de dejar de ofrecer el servicio, la dificultad de mover la carga entre nodos… Hasta hace varios años, existían tres alternativas para la orquestación, siendo todas ellas de software libre: Docker Swarm Apache Mesos Hashicorp Nomad Sin embargo, hoy en día se acepta generalmente que el vencedor de dicha batalla ha sido Kubernetes, aunque el resto de ellos siguen activos como alternativas más sencillas a k8s (Kubernetes) o en su propio nicho. Gracias a este proyecto, vamos a poder gestionar el despliegue de aplicaciones sobre contenedores, automatizando dicho despliegue y haciendo un gran énfasis en la escalabilidad, controlando en todo momento su ciclo de vida. Despliegue rápido de aplicaciones, escalabilidad de las aplicaciones al vuelo, integración de cambios sin interrupciones y posibilidad de limitar los recursos a utilizar son algunas de las características más interesantes de esta tecnología. Es un proyecto totalmente extensible, gracias a la gran multitud de módulos y plugins que se ponen a nuestra total disposición y a través del cuál gestionaremos un cluster de nodos en los que podremos, como previamente he mencionado, desplegar aplicaciones sobre contenedores. Dentro de nuestro cluster encontraremos una serie de componentes conocidos como workers, que son aquellas máquinas (virtuales o físicas) que ofrecerán la potencia de cómputo necesaria para desplegar nuestras aplicaciones. Todas ellas se encontrarán gestionadas por un nodo superior, conocido como controller. Cada uno de los nodos existentes tendrán una serie de características: Direcciones: Hostname, IP externa, IP interna… Condición: Campo que describe el estado del nodo (listo, sin red, sin disco…). Capacidad: Describe los recursos disponibles. Info: Información general (kernel, software instalado, versiones…). Existen una serie de proyectos propios de k8s que nos permiten realizar una instalación de Kubernetes de una forma muy sencilla, como pueden ser Minikube, Kubeadm o k3s. En este caso, utilizaremos k3s, una distribución de Kubernetes muy ligera que incluye a su vez todas las herramientas necesarias para gestionar nuestro cluster. Entre las herramientas que se nos proporcionan se incluye kubectl, una herramienta de línea de comandos desarrollada en Go para gestionar nuestros clusters de k8s de forma centralizada, permitiéndonos además, en caso de ser necesario, interactuar de forma directa con la API utilizando las credenciales del usuario. Dado que Kubernetes es una tecnología que cambia de forma radical el concepto de despliegue de aplicaciones que teníamos hasta ahora, debemos conocer una serie de conceptos antes de empezar a trabajar con el mismo, ya que requiere un cambio de mentalidad para adaptarnos cuanto antes: Pod: Un pod es la unidad más pequeña de Kubernetes, “equivalente” a un contenedor de Docker. Los pods, al igual que los contenedores de Docker, son efímeros, pues cuando se destruyen pierden toda la información que contenían. Si queremos que la información persista, debemos utilizar volúmenes. ReplicaSet: Un ReplicaSet es un recurso de nivel superior que asegura que siempre se ejecute un número de réplicas de un pod determinado, asegurándonos, por tanto, que un conjunto de pods siempre estén funcionando y disponibles. Se podría resumir en que nos ofrece tolerancia a fallos y escalabilidad dinámica. Deployment: Un deployment es la unidad de más alto nivel que podemos gestionar en Kubernetes, y nos ofrece control de réplicas, escalabilidad de pods, actualizaciones continuas, despliegues automáticos y rollback a versiones anteriores. A grosso modo, el deployment gestiona los ReplicaSet para que ofrezcan en todo momento el servicio con las características concretas que deseemos. Para aclarar un poco los conceptos, vamos a apreciar de forma gráfica las diferencias entre el despliegue habitual de una aplicación frente el despliegue de una aplicación haciendo uso de Kubernetes: Despliegue habitual: Despliegue Kubernetes: Como se puede apreciar, en la primera imagen tenemos un conjunto de máquinas (Frontend) que están expuestas al exterior y la carga se balancea entre ellas a través de un balanceador de carga. Dichas máquinas interactuan con otras máquinas internas (Backend) para responder las peticiones entrantes. De otro lado, en el despliegue de Kubernetes, tenemos un componente Ingress que es el que se expone al exterior (posteriormente hablamos del mismo), que es el encargado de enviar dichas peticiones al servicio Frontend (NodePort) para su correspondiente balanceo entre los pods existentes en el ReplicaSet del deployment, que como se puede suponer, el número de pods existente variará, en caso de así configurarlo, según la carga existente. Al ser una aplicación, tendrá que interactuar con los pods existentes en el otro deployment (normalmente bases de datos) para gestionar las peticiones, a través del servicio Backend (ClusterIP). Si todavía no ha quedado claro del todo, no hay problema, ya que dentro de poco vamos a llevar a cabo un ejemplo en el que se entenderá todo a la perfección. Antes de ello, y una vez entendida la parte teórica, es necesario realizar la configuración inicial de nuestro cluster de Kubernetes. Para ello, he generado un pequeño escenario compuesto por las siguientes máquinas: controller: Máquina conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.142. Actuará como controller y tiene 3 GB de RAM y 2 cores de CPU. worker1: Máquina conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.143. Actuará como worker y tiene 3 GB de RAM y 2 cores de CPU. worker2: Máquina conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.144. Actuará como worker y tiene 3 GB de RAM y 2 cores de CPU. Me encuentro actualmente haciendo uso de la máquina controller, de manera que lo primero que haremos será llevar a cabo la instalación del software necesario para gestionar nuestro cluster. Para ello, disponemos de dos opciones: Utilizar el script de instalación, pensado para instalarlo como un servicio en máquinas que ejecuten systemd, pues como servicio que es, se configurará para estar en todo momento funcionando junto a la máquina anfitriona. Además de ello, se nos proporcionarán las herramientas de gestión entre las que se encuentra kubectl. Descargar la última release del repositorio de GitHub y llevar a cabo la configuración de forma manual. Como se puede suponer, por comodidad y flexibilidad, la primera de las opciones es la elegida en este caso. Para llevar a cabo la instalación, tendremos que ejecutar el siguiente comando (en caso de no tenerlo, es necesario instalar curl): root@controller:~# curl -sfL https://get.k3s.io | sh - [INFO] Finding release for channel stable [INFO] Using v1.20.4+k3s1 as release [INFO] Downloading hash https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt [INFO] Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s [INFO] Verifying binary download [INFO] Installing k3s to /usr/local/bin/k3s [INFO] Creating /usr/local/bin/kubectl symlink to k3s [INFO] Creating /usr/local/bin/crictl symlink to k3s [INFO] Creating /usr/local/bin/ctr symlink to k3s [INFO] Creating killall script /usr/local/bin/k3s-killall.sh [INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh [INFO] env: Creating environment file /etc/systemd/system/k3s.service.env [INFO] systemd: Creating service file /etc/systemd/system/k3s.service [INFO] systemd: Enabling k3s unit Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service. [INFO] systemd: Starting k3s En consecuencia del proceso que está actualmente en ejecución, se habrá abierto un socket TCP/IP en el puerto por defecto que utiliza k3s (6443) que estará escuchando peticiones en todas las interfaces de la máquina, así que para verificarlo haremos uso del comando: root@controller:~# netstat -tlnp | egrep '6443' tcp6 0 0 :::6443 :::* LISTEN 445/k3s server Donde: -t: Filtramos únicamente para las conexiones que utilizan el protocolo TCP. -l: Filtramos únicamente para los sockets que están actualmente escuchando peticiones (State = LISTEN). -n: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos. -p: Indicamos que muestre el PID y el nombre del proceso al que pertenece dicho socket. Efectivamente, el proceso está escuchando peticiones tal y como debería en el puerto 6443/TCP, que será el utilizado para la posterior conexión con los nodos worker. Como resultado de la instalación, algunos binarios se habrán instalado en el directorio /usr/local/bin/ y estarán ahora disponibles para su uso. Para verificarlo, haremos uso del comando: root@controller:~# ls -l /usr/local/bin/ total 44296 lrwxrwxrwx 1 root root 3 Mar 4 07:40 crictl -&gt; k3s lrwxrwxrwx 1 root root 3 Mar 4 07:40 ctr -&gt; k3s -rwxr-xr-x 1 root root 45350912 Mar 4 07:40 k3s -rwxr-xr-x 1 root root 1702 Mar 4 07:40 k3s-killall.sh -rwxr-xr-x 1 root root 1037 Mar 4 07:40 k3s-uninstall.sh lrwxrwxrwx 1 root root 3 Mar 4 07:40 kubectl -&gt; k3s Como era de esperar, nuevos binarios han sido instalados para su correspondiente uso, entre los que se encuentra kubectl, que nos permitirá gestionar mediante línea de comandos y de forma centralizada, nuestro cluster. Para comprobar que está funcionando correctamente, vamos a listar todos los nodos existentes en el cluster, ejecutando para ello el comando: root@controller:~# kubectl get nodes NAME STATUS ROLES AGE VERSION controller Ready control-plane,master 37s v1.20.4+k3s1 Actualmente, existe un único nodo en el cluster, concretamente el controlador que es la máquina desde la que hemos ejecutado el comando. Sin embargo, esto no es lo que queremos, ya que tenemos otras dos máquinas que actuarán como workers y necesitamos integrarla al mismo. Por motivos de seguridad, para vincular un nuevo nodo a un cluster, necesitamos hacer uso además de la dirección IP del controller, de un token único de verificación que podremos encontrar en el fichero de nombre /var/lib/rancher/k3s/server/node-token, de manera que visualizaremos su contenido haciendo uso del comando: root@controller:~# cat /var/lib/rancher/k3s/server/node-token K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a Todo está listo para llevar a cabo la instalación de k3s en los nodos workers, haciendo uso una vez más del método de instalación anterior. Sin embargo, durante la misma, tendremos que indicar de forma obligatoria dos parámetros, para así poder llevar a cabo la vinculación con el nodo controller y hacer que dichas máquinas, actúen, como se puede suponer, como workers: K3S_URL: Indicamos la URL de conexión al controller, que se generará siguiendo la sintaxis https://[IP]:6443. En este caso, la URL final sería https://192.168.1.142:6443. K3S_TOKEN: Indicamos el token del nodo controlador previamente visualizado. Nota: En caso de que los hostname de las máquinas worker coincidiesen, habría que diferenciarlas utilizando también el parámetro K3S_NODE_NAME durante la ejecución del script de instalación. De esta manera, los comandos a ejecutar para instalar k3s en ambos nodos worker y vincularlos al controller serían (en caso de no tenerlo, es necesario instalar curl): root@worker1:~# curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.142:6443 K3S_TOKEN=K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a sh - [INFO] Finding release for channel stable [INFO] Using v1.20.4+k3s1 as release [INFO] Downloading hash https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt [INFO] Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s [INFO] Verifying binary download [INFO] Installing k3s to /usr/local/bin/k3s [INFO] Creating /usr/local/bin/kubectl symlink to k3s [INFO] Creating /usr/local/bin/crictl symlink to k3s [INFO] Creating /usr/local/bin/ctr symlink to k3s [INFO] Creating killall script /usr/local/bin/k3s-killall.sh [INFO] Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh [INFO] env: Creating environment file /etc/systemd/system/k3s-agent.service.env [INFO] systemd: Creating service file /etc/systemd/system/k3s-agent.service [INFO] systemd: Enabling k3s-agent unit Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service. [INFO] systemd: Starting k3s-agent root@worker2:~# curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.142:6443 K3S_TOKEN=K1007e764762d120d437ad0ab8460e3710b2b8fb110f74829caf5b5d5e6ba41e1dc::server:da0b3b9a53d12bb2192394d61a59308a sh - [INFO] Finding release for channel stable [INFO] Using v1.20.4+k3s1 as release [INFO] Downloading hash https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/sha256sum-amd64.txt [INFO] Downloading binary https://github.com/rancher/k3s/releases/download/v1.20.4+k3s1/k3s [INFO] Verifying binary download [INFO] Installing k3s to /usr/local/bin/k3s [INFO] Creating /usr/local/bin/kubectl symlink to k3s [INFO] Creating /usr/local/bin/crictl symlink to k3s [INFO] Creating /usr/local/bin/ctr symlink to k3s [INFO] Creating killall script /usr/local/bin/k3s-killall.sh [INFO] Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh [INFO] env: Creating environment file /etc/systemd/system/k3s-agent.service.env [INFO] systemd: Creating service file /etc/systemd/system/k3s-agent.service [INFO] systemd: Enabling k3s-agent unit Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service. [INFO] systemd: Starting k3s-agent Una vez finalizada la instalación de la distribución de Kubernetes en todos los nodos, volveremos al nodo controlador para verificar que la conexión con el resto de máquinas se ha llevado a cabo correctamente y ahora se encuentran a su disposición, haciendo para ello uso del comando: root@controller:~# kubectl get nodes NAME STATUS ROLES AGE VERSION controller Ready control-plane,master 12m v1.20.4+k3s1 worker1 Ready &lt;none&gt; 101s v1.20.4+k3s1 worker2 Ready &lt;none&gt; 16s v1.20.4+k3s1 Como era de esperar, nuestro cluster de Kubernetes está ahora compuesto por un total de tres nodos: un controller y dos worker. Sin embargo, si lo pensamos, gestionar nuestro cluster desde el nodo controlador puede llegar a ser un tanto incómodo, ya que tenemos que conectarnos al mismo cada vez que queramos llevar a cabo cualquier acción. La solución más sencilla a esto consiste en instalar kubectl en la máquina anfitriona, para así gestionar de forma remota el cluster. Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de Kubernetes. Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando: root@debian:~# echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" &gt; /etc/apt/sources.list.d/kubernetes.list De otro lado, tendremos que añadir también la clave pública GPG de Google a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de Kubernetes que vamos a descargar, haciendo para ello uso del comando: root@debian:~# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - OK Tras ello, podremos dar paso a la instalación de kubectl, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando: root@debian:~# apt update &amp;&amp; apt install kubectl El paquete kubectl ha sido instalado en la máquina, de manera que vamos a verificar la versión del mismo para así comprobar su correcto funcionamiento, haciendo para ello uso del comando: root@debian:~# kubectl version Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.4", GitCommit:"e87da0bd6e03ec3fea7933c4b5263d151aafd07c", GitTreeState:"clean", BuildDate:"2021-02-18T16:12:00Z", GoVersion:"go1.15.8", Compiler:"gc", Platform:"linux/amd64"} The connection to the server localhost:8080 was refused - did you specify the right host or port? Actualmente, nos encontramos haciendo uso de la versión v1.20.4, que es la misma que la instalada en los nodos del cluster, de manera que vamos a disponer de un soporte completo, al estar compartiendo la misma versión. Si apreciamos la última línea, hemos obtenido un error de conexión rehusada. Es totalmente normal, ya que kubectl está tratando de acceder a nuestro cluster local, sin embargo, no existe ningún cluster en ejecución de forma local. Para solucionarlo, necesitaremos un fichero config con las credenciales para el acceso al cluster remoto, que podremos encontrar en /etc/rancher/k3s/k3s.yaml en el nodo controlador, que debemos ubicar dentro de un directorio de nombre ~/.kube en la máquina anfitriona, el cuál generaremos ejecutando el comando: root@debian:~# mkdir .kube Una vez generado, podremos llevar a cabo la copia de dicho fichero desde el nodo controlador al directorio que acabamos de generar haciendo para ello uso del comando: root@debian:~# scp root@192.168.1.142:/etc/rancher/k3s/k3s.yaml .kube/config Una vez completada la transferencia, visualizaremos el contenido de dicho fichero mediante la ejecución del comando: root@debian:~# cat .kube/config apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUyTVRRNE5ETTJNRFV3SGhjTk1qRXdNekEwTURjME1EQTFXaGNOTXpFd016QXlNRGMwTURBMQpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUyTVRRNE5ETTJNRFV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFTMXJNTU1ocWlGL0hDM1ZJWGtTNzlBQTRoZ1FYbkFMVkp3UVVMd1ArK0IKQXA5NFpwWFR0cDZTNnNDVFBxTCs5ZE5CcTBhYW9sSlBDWnN6UWNBWTFYdUJvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWMzNDhZOHErZzFMOEVxY1VpQTVNCmZCaEt6cDB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUs4RHp4T0p4M01jOVlkUHdaemhkU1h3Nnk3UlZtbnMKQ09qalExVGZXc2FSQWlFQW5TcUpEbVpnekNTSnZsUndXVVVEc0piQXZZTHBLdUJ3TytPM3QxVGhOR0k9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K server: https://127.0.0.1:6443 name: default contexts: - context: cluster: default user: default name: default current-context: default kind: Config preferences: {} users: - name: default user: client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNDQVRlZ0F3SUJBZ0lJZS84WU50UTZwd0l3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOakUwT0RRek5qQTFNQjRYRFRJeE1ETXdOREEzTkRBd05Wb1hEVEl5TURNdwpOREEzTkRBd05Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJNbzZDNTFwR3dBK2pEbGoKdFhYYUI2TkhoNnNmbXpUK2NkZnZZKzlzbGFBYms2Z1VjdFZvdUt4Q3JvRkFjeXN1b0UzM21HRlJkMEJqT2ViTwovZXh6YW42alNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUUhsZmJ3RTBEN1RlSHVDWnBlTkMvbTVRT3pHVEFLQmdncWhrak9QUVFEQWdOSkFEQkcKQWlFQWxTQ25ZRWJWbzVFYkdnZHpWcEhrbHZTdTNoSGdzSGRyOUgvOXJibmltUUFDSVFETVZCL056aGw4azF5ZApyNFZhQmY0OGJKenRSdWdUWjY2amxsdHB6c3orTEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZHpDQ0FSMmdBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwClpXNTBMV05oUURFMk1UUTRORE0yTURVd0hoY05NakV3TXpBME1EYzBNREExV2hjTk16RXdNekF5TURjME1EQTEKV2pBak1TRXdId1lEVlFRRERCaHJNM010WTJ4cFpXNTBMV05oUURFMk1UUTRORE0yTURVd1dUQVRCZ2NxaGtqTwpQUUlCQmdncWhrak9QUU1CQndOQ0FBUjgwMWVvY3JsQ09FNkZ1WmplQWxPVHFJTDhnM1l5bDltelI3UUdoQUlpCkFnNmovVkpRYzhXb2w1eHRWdUNBeGViODIvUDVSYlBkMHpzdnlVYlFxTk1ObzBJd1FEQU9CZ05WSFE4QkFmOEUKQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVCNVgyOEJOQSswM2g3Z21hWGpRdgo1dVVEc3hrd0NnWUlLb1pJemowRUF3SURTQUF3UlFJaEFNVkhMSmIyeDVuUzlMN2FUbEhYZ0V3QUp5U3F5UnNPClRTcDRoUGJoaG9NVkFpQUhlZEEzeEwxcTJ4aTNQaU9vQ2RiZnZzT2d2WCsrTE41QWNBUjYrNW1jNFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU1kd0RNc2ZsMlB0SjI4NDZlcTlVTlVwRk5HTVVwc3N1UmlkNnQ5dEZpUE5vQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFeWpvTG5Xa2JBRDZNT1dPMWRkb0hvMGVIcXgrYk5QNXgxKzlqNzJ5Vm9CdVRxQlJ5MVdpNApyRUt1Z1VCekt5NmdUZmVZWVZGM1FHTTU1czc5N0hOcWZnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= Como se puede apreciar, el mismo contiene algunas credenciales para el acceso remoto al cluster, que en mi caso, no me importa mostrar de forma pública ya que es un cluster de ejemplo y que será eliminado una vez finalizada la práctica. Sin embargo, existe una directiva server que no se encuentra correctamente configurada, ya que está apuntando a la dirección de la máquina local (127.0.0.1), a pesar de que debería estar apuntando al nodo controlador (192.168.1.142). Para solucionarlo, modificaremos el fichero haciendo uso del comando: root@debian:~# nano .kube/config El resultado final de dicha directiva, tras llevar a cabo la correspondiente modificación debería ser: server: https://192.168.1.142:6443 Toda la configuración en la máquina anfitriona para poder gestionar de forma remota el cluster ha finalizado, de manera que comprobaremos que tenemos acceso al mismo listando una vez más los nodos existentes en dicho cluster, ejecutando para ello el comando: root@debian:~# kubectl get nodes NAME STATUS ROLES AGE VERSION controller Ready control-plane,master 30m v1.20.4+k3s1 worker1 Ready &lt;none&gt; 19m v1.20.4+k3s1 worker2 Ready &lt;none&gt; 18m v1.20.4+k3s1 Efectivamente, así ha sido, de manera que podemos concluir que tenemos acceso remoto a nuestro cluster de Kubernetes. Todo está listo para empezar la parte interesante del artículo, en la que vamos a realizar un despliegue de una aplicación conocida como “Let’s Chat”, utilizando para ello el repositorio de GitHub del IES Gonzalo Nazareno, que nos proporcionará todos los ficheros .yaml para definir los deployment, los servicios… En mi caso, me he movido al directorio /home/alvaro/GitHub/ de la máquina anfitriona haciendo uso de cd, pues será donde clonaré dicho repositorio, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub# git clone https://github.com/iesgn/kubernetes-storm.git Clonando en 'kubernetes-storm'... remote: Enumerating objects: 288, done. remote: Counting objects: 100% (288/288), done. remote: Compressing objects: 100% (213/213), done. remote: Total 288 (delta 119), reused 224 (delta 60), pack-reused 0 Recibiendo objetos: 100% (288/288), 6.36 MiB | 9.76 MiB/s, listo. Resolviendo deltas: 100% (119/119), listo. Una vez finalizada la clonación, nos moveremos dentro del mismo, concretamente al directorio unidad3/ejemplos-3.2/ejemplo8/, pues es donde se encuentra la aplicación en cuestión, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub# cd kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8/ Para entrar en sintonía, vamos a listar el contenido del directorio actual, para así comprobar los distintos ficheros de los que disponemos, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# ls -l total 20 -rw-r--r-- 1 root root 247 mar 4 09:12 ingress.yaml -rw-r--r-- 1 root root 394 mar 4 09:12 letschat-deployment.yaml -rw-r--r-- 1 root root 177 mar 4 09:12 letschat-srv.yaml -rw-r--r-- 1 root root 358 mar 4 09:12 mongo-deployment.yaml -rw-r--r-- 1 root root 149 mar 4 09:12 mongo-srv.yaml Como se puede apreciar, tenemos un total de 5 ficheros, los cuales iremos explicando con mayor detenimiento durante el transcurso de la tarea. El primero en el que debemos fijarnos es mongo-deployment.yaml, de manera que visualizaremos el contenido del mismo, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# cat mongo-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: mongo labels: name: mongo spec: replicas: 1 selector: matchLabels: name: mongo template: metadata: labels: name: mongo spec: containers: - name: mongo image: mongo ports: - name: mongo containerPort: 27017 Sin entrar en demasiado detalle, dentro del mismo hemos definido un deployment que generará por consecuencia un ReplicaSet con un único pod, por ahora, que ejecutará una imagen mongo que ofrecerá su servicio en el puerto 27017. Gracias al mismo, estamos definiendo la parte Backend de nuestra aplicación, con la que posteriormente interactuará el Frontend para ofrecer el servicio. Todo está listo para definir el deployment a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, haremos uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply -f mongo-deployment.yaml deployment.apps/mongo created Como se puede apreciar en la salida del comando ejecutado, el deployment ha sido correctamente definido, de manera que vamos a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 51s mongo mongo name=mongo NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 50s mongo mongo name=mongo,pod-template-hash=5c694c878b NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-46ts2 1/1 Running 0 50s 10.42.2.4 worker2 &lt;none&gt; &lt;none&gt; Tal y como he mencionado previamente, el deployment que he definido ha generado a su vez un ReplicaSet asociado que contiene un único pod con la imagen de mongo, que como se puede apreciar, se está ejecutando sobre el nodo worker2. Para que empecemos a comprender el potencial de Kubernetes, vamos a tratar de eliminar el pod asociado al ReplicaSet que se ha generado, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl delete pod/mongo-5c694c878b-46ts2 pod "mongo-5c694c878b-46ts2" deleted En un principio, basándonos en la salida del comando ejecutado, podríamos concluir que el pod ha sido eliminado y que por tanto, se ha dejado de ofrecer el servicio deseado. Vamos a volver a listar los objetos existentes en nuestro cluster, ejecutando para ello el comando utilizado con anterioridad: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 3m33s mongo mongo name=mongo NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 3m32s mongo mongo name=mongo,pod-template-hash=5c694c878b NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Running 0 13s 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; ¿Magia? Podríamos decir que sí. Kubernetes, no conforme con nuestra decisión de eliminar el pod asociado, ha generado un nuevo pod de forma inmediata que de nuevo se está ejecutando en el nodo worker2 (podría haberse ejecutado en el nodo worker1, pero ha dado la casualidad), de manera que nuestro servicio vuelve a estar operativo de forma casi inmediata. Existe un concepto que todavía no hemos introducido, y que nos será necesario para continuar, los servicios. Los servicios (services) nos permiten acceder a nuestras aplicaciones, ya que proporcionan una capa de abstracción que define un conjunto de pods que implementan un micro-servicio. Nos ofrecen una dirección IP virtual y un nombre que identifica al conjunto de pods que representa, al cuál nos podremos conectar. Dependiendo del tipo de servicio, NodePort o ClusterIP, permitiremos el acceso desde el exterior desde un puerto aleatorio del controller o únicamente de forma interna desde otros pods, respectivamente. Una vez comprendido el concepto servicio, podremos proceder a definir el contenido del fichero mongo-srv.yaml, que como ya hemos mencionado, proporcionará una dirección IP virtual para acceder de una forma totalmente abstracta y ajena a nosotros a los pods existentes en el ReplicaSet, pues se encargará de “balancear” la carga y enviar las peticiones únicamente a aquellos pods que se encuentren activos, para asegurar que el servicio funcione en todo momento, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# cat mongo-srv.yaml apiVersion: v1 kind: Service metadata: name: mongo spec: ports: - name: mongo port: 27017 targetPort: mongo selector: name: mongo Sin entrar en demasiado detalle, dentro del mismo hemos definido un service de tipo ClusterIP, es decir, que únicamente será accesible de forma interna por el resto de pods (valor por defecto) y ofrecerá su servicio en el puerto 27017 afectando a aquellas máquinas que están ejecutando mongo. Todo está listo para definir el service a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, ejecutaremos el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply -f mongo-srv.yaml service/mongo created Como se puede apreciar en la salida del comando ejecutado, el service ha sido correctamente definido, de manera que vamos a listar todos los servicios existentes en nuestro cluster de Kubernetes, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 &lt;none&gt; 443/TCP 50m mongo ClusterIP 10.43.10.91 &lt;none&gt; 27017/TCP 20s Efectivamente, se ha generado un nuevo servicio de nombre mongo únicamente accesible a través de la dirección del Cluster IP para los pods internos, pues se trata de un servicio crítico y debemos priorizar en todo momento su seguridad. Ha llegado la hora de desplegar la aplicación Let’s Chat como tal, deployment que se encontrará definido en el fichero letschat-deployment.yaml, de manera que visualizaremos el contenido del mismo, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# cat letschat-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: letschat labels: name: letschat spec: replicas: 1 selector: matchLabels: name: letschat template: metadata: labels: name: letschat spec: containers: - name: letschat image: sdelements/lets-chat ports: - name: http-server containerPort: 8080 Sin entrar en demasiado detalle, dentro del mismo hemos definido un deployment que generará por consecuencia un ReplicaSet con un único pod, por ahora, que ejecutará una imagen sdelements/lets-chat que ofrecerá su servicio en el puerto 8080. Gracias al mismo, estamos definiendo la parte Frontend de nuestra aplicación, que interactuará con la parte Backend previamente definida para ofrecer el servicio. Todo está listo para definir el deployment a partir de dicho fichero. Para ello, haremos uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply -f letschat-deployment.yaml deployment.apps/letschat created Como se puede apreciar en la salida del comando ejecutado, el deployment ha sido correctamente definido, de manera que vamos a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 2m46s mongo mongo name=mongo deployment.apps/letschat 1/1 1 1 8s letschat sdelements/lets-chat name=letschat NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 2m45s mongo mongo name=mongo,pod-template-hash=5c694c878b replicaset.apps/letschat-7c66bd64f5 1 1 1 8s letschat sdelements/lets-chat name=letschat,pod-template-hash=7c66bd64f5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Running 0 2m45s 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-6mt2m 1/1 Running 0 8s 10.42.1.5 worker1 &lt;none&gt; &lt;none&gt; Tal y como he mencionado previamente, el deployment que he definido ha generado a su vez un ReplicaSet asociado que contiene un único pod con la imagen de sdelements/lets-chat, que como se puede apreciar, se está ejecutando sobre el nodo worker1. Una vez definida nuestra aplicación, podremos desplegar el servicio existente en el fichero letschat-srv.yaml, exponiendo así la parte Frontend de nuestra aplicación al exterior para así hacerla accesible, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# cat letschat-srv.yaml apiVersion: v1 kind: Service metadata: name: letschat spec: type: NodePort ports: - name: http port: 8080 targetPort: http-server selector: name: letschat Sin entrar en demasiado detalle, dentro del mismo hemos definido un service de tipo NodePort, es decir, que será accesible de forma externa y redirigirá las peticiones al puerto 8080 de aquellas máquinas que están ejecutando letschat. Todo está listo para definir el service a partir de dicho fichero. Para ello, ejecutaremos el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply -f letschat-srv.yaml service/letschat created Como se puede apreciar en la salida del comando ejecutado, el service ha sido correctamente definido, de manera que vamos a listar todos los servicios existentes en nuestro cluster de Kubernetes, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 &lt;none&gt; 443/TCP 53m mongo ClusterIP 10.43.10.91 &lt;none&gt; 27017/TCP 3m25s letschat NodePort 10.43.78.152 &lt;none&gt; 8080:32468/TCP 16s Efectivamente, se ha generado un nuevo servicio de nombre letschat accesible desde el exterior a través del puerto 32468 de la máquina controladora. Sin embargo, esto no es para nada práctico, pues no es viable hacer a los clientes conectarse de forma manual al puerto 32468 en cada conexión, poniendo al final de la URL ‘:32468’. Para solucionarlo, haremos uso de los Ingress controller. Los controladores Ingress o Ingress controller nos permiten utilizar un proxy inverso que por medio de reglas de encaminamiento que obtiene de la API de Kubernetes nos permite el acceso a nuestras aplicaciones por medio de nombres. Una vez comprendido el concepto ingress, podremos proceder a definir el contenido del fichero ingress.yaml, que como ya hemos mencionado, proporcionará un proxy inverso para acceder a través de un nombre a la aplicación que hemos desplegado, proporcionando por tanto una capa de abstracción más, de manera que visualizaremos el contenido del mismo, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# cat ingress.yaml apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: ingress-letschat spec: rules: - host: www.letschat.com http: paths: - path: "/" backend: serviceName: letschat servicePort: 8080 En este caso, la versión de la API que utiliza el fichero se encuentra casi obsoleta, de manera que he decidido actualizar el contenido para adaptarlo, siguiendo la documentación, a la versión v1. Para modificar el fichero ejecutaremos el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# nano ingress.yaml El resultado final del fichero, tras llevar a cabo las correspondientes modificaciones sería: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-letschat spec: rules: - host: www.letschat.com http: paths: - path: "/" pathType: Prefix backend: service: name: letschat port: number: 8080 Sin entrar en demasiado detalle, dentro del mismo hemos definido un ingress que será accesible a través del nombre www.letschat.com afectando a aquellas máquinas que están ejecutando letschat, en el puerto 8080. Todo está listo para definir el ingress a partir de dicho fichero (es importante conocer que también es posible hacerlo mediante una instrucción de línea de comandos, aunque no es lo común). Para ello, ejecutaremos el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl apply -f ingress.yaml ingress.networking.k8s.io/ingress-letschat created Como se puede apreciar en la salida del comando ejecutado, el ingress ha sido correctamente definido, de manera que vamos a listar todos los ingress existentes en nuestro cluster de Kubernetes, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE ingress-letschat &lt;none&gt; www.letschat.com 192.168.1.142 80 70s Efectivamente, se ha generado un nuevo ingress de nombre ingress-letschat accesible a través del nombre www.letschat.com que deberá apuntar a la dirección IP del nodo controlador 192.168.1.142. Para hacer que el nombre www.letschat.com se resuelva a la dirección IP deseada, modificaremos el fichero de resolución estática de nombres /etc/hosts, indicando una nueva entrada para el mismo. Para modificar dicho fichero, ejecutaremos el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# nano /etc/hosts El resultado del mismo, tras añadir la nueva correspondencia entre nombre y dirección IP sería: 127.0.0.1 localhost 127.0.1.1 debian 192.168.1.142 www.letschat.com # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters Todo está listo para tratar de acceder a http://www.letschat.com a través de nuestro navegador, pudiendo apreciar lo siguiente Como era de esperar, la aplicación desplegada en Kubernetes se encuentra totalmente operativa y podemos acceder a la misma a través de un nombre de dominio. Sin embargo, esto no es todo, ya que como bien sabemos, Kubernetes nos ofrece una flexibilidad impresionante sobre el cluster existente, como por ejemplo escalar el número de pods existente en el deployment referente a la aplicación Let’s Chat para así poder atender un mayor número de peticiones de forma concurrente. En este caso, vamos a escalar a un total de 10 pods, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl scale deploy letschat --replicas=10 deployment.apps/letschat scaled Una vez escalada la aplicación, volveremos a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 34m mongo mongo name=mongo deployment.apps/letschat 10/10 10 10 31m letschat sdelements/lets-chat name=letschat NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 34m mongo mongo name=mongo,pod-template-hash=5c694c878b replicaset.apps/letschat-7c66bd64f5 10 10 10 31m letschat sdelements/lets-chat name=letschat,pod-template-hash=7c66bd64f5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Running 0 30m 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wg4cs 1/1 Running 0 8m53s 10.42.0.8 controller &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-d4ccg 1/1 Running 0 11s 10.42.1.28 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-x2s95 1/1 Running 0 11s 10.42.2.25 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-68fgp 1/1 Running 0 11s 10.42.2.23 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-2mlbn 1/1 Running 0 11s 10.42.2.24 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-gfsf5 1/1 Running 0 11s 10.42.2.26 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wtzhq 1/1 Running 0 11s 10.42.1.29 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-pvskq 1/1 Running 0 11s 10.42.1.30 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-fqjrz 1/1 Running 0 11s 10.42.1.27 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-h2zbg 1/1 Running 0 11s 10.42.1.31 worker1 &lt;none&gt; &lt;none&gt; Como se puede apreciar, el número de pods referentes al ReplicaSet letschat ha aumentado de 1 a 10, repartiéndose los mismos entre los dos nodos worker principalmente, aunque cuando aumentamos muy considerablemente la carga en los mismos, puede ocurrir dependiendo de la distribución de k8s que algunos pods se ejecuten en el nodo controlador para así desahogarlos un poco. En consecuencia, nuestra aplicación puede soportar ahora una mayor cantidad de tráfico. Por último, vamos a simular una situación real en la que uno de los nodos worker cayese, para así apreciar como k8s es capaz de gestionar la situación levantando ahora todos los pods entre el worker y el controller para así no dejar de ofrecer el servicio en ningún momento. En mi caso, al estar haciendo uso de un escenario en vagrant, bastaría con ejecutar la siguiente instrucción para apagar el nodo worker2: alvaro@debian:~/vagrant/kubernetes$ vagrant halt worker2 Si tras ello volvemos a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, podremos apreciar lo siguiente: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 0/1 1 0 39m mongo mongo name=mongo deployment.apps/letschat 1/10 10 1 37m letschat sdelements/lets-chat name=letschat NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 0 39m mongo mongo name=mongo,pod-template-hash=5c694c878b replicaset.apps/letschat-7c66bd64f5 10 10 1 37m letschat sdelements/lets-chat name=letschat,pod-template-hash=7c66bd64f5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Running 0 36m 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-x2s95 1/1 Running 0 5m38s 10.42.2.25 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-gfsf5 1/1 Running 0 5m38s 10.42.2.26 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-68fgp 1/1 Running 0 5m38s 10.42.2.23 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wtzhq 0/1 CrashLoopBackOff 4 5m38s 10.42.1.29 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-pvskq 0/1 CrashLoopBackOff 4 5m38s 10.42.1.30 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-h2zbg 0/1 CrashLoopBackOff 4 5m38s 10.42.1.31 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-d4ccg 0/1 CrashLoopBackOff 4 5m38s 10.42.1.28 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-fqjrz 0/1 CrashLoopBackOff 4 5m38s 10.42.1.27 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wg4cs 0/1 CrashLoopBackOff 4 14m 10.42.0.8 controller &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-w75xr 1/1 Running 5 4m57s 10.42.0.12 controller &lt;none&gt; &lt;none&gt; Como podemos apreciar, los pods en ejecución en el nodo worker1 están empezando a fallar, ya que no tienen conexión con el Backend que estaba ubicado en el nodo worker2 que ha caído. Esto es importante tenerlo en cuenta, ya que el backend también debe estar configurado en alta disponibilidad para evitar puntos de fallo únicos como en este caso. Por defecto, existe un parámetro de nombre pod-eviction-timeout que especifica el tiempo que ha de transcurrir hasta que los pods se muevan a otro nodo worker en caso de dejar de funcionar aquel en el que se estaban ejecutando, cuyo valor es de 5 minutos. Dependiendo de nuestras circunstancias, tendremos que modificar dicho valor para adaptarlo a nuestras necesidades, intentando que no sea muy pequeño ni tampoco muy grande. Tras haber pasado alrededor de 5 minutos, podremos volver a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, pudiendo apreciar lo siguiente: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 44m mongo mongo name=mongo deployment.apps/letschat 10/10 10 10 41m letschat sdelements/lets-chat name=letschat NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 44m mongo mongo name=mongo,pod-template-hash=5c694c878b replicaset.apps/letschat-7c66bd64f5 10 10 10 41m letschat sdelements/lets-chat name=letschat,pod-template-hash=7c66bd64f5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Terminating 0 41m 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-gfsf5 1/1 Terminating 0 10m 10.42.2.26 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-x2s95 1/1 Terminating 0 10m 10.42.2.25 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-68fgp 1/1 Terminating 0 10m 10.42.2.23 worker2 &lt;none&gt; &lt;none&gt; pod/mongo-5c694c878b-rnnr2 1/1 Running 0 2m51s 10.42.1.33 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-9s8dw 1/1 Running 2 2m51s 10.42.1.32 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-ljcw6 1/1 Running 2 2m51s 10.42.0.14 controller &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-tdcpq 1/1 Running 2 2m51s 10.42.0.13 controller &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-w75xr 1/1 Running 6 9m47s 10.42.0.12 controller &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wtzhq 1/1 Running 6 10m 10.42.1.29 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-pvskq 1/1 Running 6 10m 10.42.1.30 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-d4ccg 1/1 Running 6 10m 10.42.1.28 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-h2zbg 1/1 Running 6 10m 10.42.1.31 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-fqjrz 1/1 Running 6 10m 10.42.1.27 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-wg4cs 1/1 Running 6 19m 10.42.0.8 controller &lt;none&gt; &lt;none&gt; Como se puede apreciar, el número de pods referentes al ReplicaSet letschat sigue siendo 10, repartiéndose los mismos entre el primer nodo worker y el nodo controlador para así desahogarlo un poco. En consecuencia, nuestra aplicación sigue estando totalmente operativa, tal y como podemos ver: Al igual que podemos escalar nuestro deployment para que tenga un mayor número de pods, también podemos desescalarlo para eliminar pods en caso de que el tráfico se haya visto reducido (generalmente esta tarea se suele delegar a un componente para hacerlo de forma automatizada). En este caso, vamos a desescalar a un total de 1 pod, haciendo para ello uso del comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl scale deploy letschat --replicas=1 deployment.apps/letschat scaled Una vez desescalada la aplicación, volveremos a listar todos los deployment, ReplicaSet y pods existentes en nuestro cluster de Kubernetes, ejecutando para ello el comando: root@debian:/home/alvaro/GitHub/kubernetes-storm/unidad3/ejemplos-3.2/ejemplo8# kubectl get deploy,rs,po -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/mongo 1/1 1 1 45m mongo mongo name=mongo deployment.apps/letschat 1/1 1 1 43m letschat sdelements/lets-chat name=letschat NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/mongo-5c694c878b 1 1 1 45m mongo mongo name=mongo,pod-template-hash=5c694c878b replicaset.apps/letschat-7c66bd64f5 1 1 1 43m letschat sdelements/lets-chat name=letschat,pod-template-hash=7c66bd64f5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/mongo-5c694c878b-5449c 1/1 Terminating 0 42m 10.42.2.5 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-gfsf5 1/1 Terminating 0 11m 10.42.2.26 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-x2s95 1/1 Terminating 0 11m 10.42.2.25 worker2 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-68fgp 1/1 Terminating 0 11m 10.42.2.23 worker2 &lt;none&gt; &lt;none&gt; pod/mongo-5c694c878b-rnnr2 1/1 Running 0 4m5s 10.42.1.33 worker1 &lt;none&gt; &lt;none&gt; pod/letschat-7c66bd64f5-ljcw6 1/1 Running 2 4m5s 10.42.0.14 controller &lt;none&gt; &lt;none&gt; Como se puede apreciar, el número de pods referentes al ReplicaSet letschat ha desminuido de 10 a 1. Los otros 4 pods son referentes al nodo worker2 que todavía no han podido eliminarse ya que la máquina se encuentra inactiva. Espero que el artículo haya contribuido a la comprensión de esta magnífica tecnología que va a ayudar a que el despliegue de las aplicaciones sea una tarea menos tediosa y podamos gestionar en todo momento de una forma muy sencilla, entre otras cosas, los despliegues y rollbacks entre versiones de la aplicación.]]></summary></entry><entry><title type="html">Proxy inverso con Docker y nginx</title><link href="https://www.alvarovf.com/servicios/2021/03/02/proxy-inverso-nginx-docker.html" rel="alternate" type="text/html" title="Proxy inverso con Docker y nginx" /><published>2021-03-02T15:33:00+00:00</published><updated>2021-03-02T15:33:00+00:00</updated><id>https://www.alvarovf.com/servicios/2021/03/02/proxy-inverso-nginx-docker</id><content type="html" xml:base="https://www.alvarovf.com/servicios/2021/03/02/proxy-inverso-nginx-docker.html"><![CDATA[<p>Instalamos <strong>docker.io</strong>, <strong>docker-compose</strong> y <strong>nginx</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>docker.io docker-compose nginx</code></pre></figure>

<p>Creamos un fichero de nombre <strong>docker-compose.yaml</strong> en el que definimos los contenedores que existirán en el escenario, dos para la aplicación <strong>joomla</strong> y otros dos para <strong>nextcloud</strong> (base de datos y aplicación):</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# nano docker-compose.yaml

version: <span class="s1">'3.1'</span>

services:

  joomla:
    container_name: servidor_joomla
    image: joomla
    restart: always
    environment:
      JOOMLA_DB_HOST: dbjoomla
      JOOMLA_DB_USER: user_joomla
      JOOMLA_DB_PASSWORD: asdasd
      JOOMLA_DB_NAME: bd_joomla
    ports:
      - 8081:80

  dbjoomla:
    container_name: db_joomla
    image: mariadb
    restart: always
    environment:
      MYSQL_DATABASE: bd_joomla
      MYSQL_USER: user_joomla
      MYSQL_PASSWORD: asdasd
      MYSQL_ROOT_PASSWORD: asdasd

  nextcloud:
    container_name: servidor_nextcloud
    image: nextcloud
    restart: always
    environment:
      MYSQL_HOST: dbnextcloud
      MYSQL_USER: user_nextcloud
      MYSQL_PASSWORD: asdasd
      MYSQL_DATABASE: bd_nextcloud
    ports:
      - 8082:80

  dbnextcloud:
    container_name: db_nextcloud
    image: mariadb
    restart: always
    environment:
      MYSQL_DATABASE: bd_nextcloud
      MYSQL_USER: user_nextcloud
      MYSQL_PASSWORD: asdasd
      MYSQL_ROOT_PASSWORD: asdasd</code></pre></figure>

<p>Levantamos el escenario que acabamos de definir:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# docker-compose up <span class="nt">-d</span>
Creating servidor_nextcloud ... <span class="k">done
</span>Creating db_nextcloud       ... <span class="k">done
</span>Creating db_joomla          ... <span class="k">done
</span>Creating servidor_joomla    ... <span class="k">done</span></code></pre></figure>

<p>Comprobamos que las aplicaciones se están sirviendo en los puertos <strong>8081</strong> y <strong>8082</strong> respectivamente, sin hacer todavía ningún proxy inverso:</p>

<p><img src="https://i.ibb.co/VDYKkjp/Captura-de-pantalla-de-2021-02-26-15-40-15.png" alt="nginx1" title="Joomla" /></p>

<p><img src="https://i.ibb.co/CsP5xGH/Captura-de-pantalla-de-2021-02-26-15-40-19.png" alt="nginx2" title="Nextcloud" /></p>

<p>Una vez que son accesibles, es hora de generar dos nuevos VirtualHost en los que haremos proxy inverso desde dos nombres de dominio distintos para acceder a dichas aplicaciones (<strong>www.app1.org</strong> y <strong>www.app2.org</strong>):</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# <span class="nb">cp</span> /etc/nginx/sites-available/default /etc/nginx/sites-available/app1
root@debian:~/proxyinverso# <span class="nb">cp</span> /etc/nginx/sites-available/default /etc/nginx/sites-available/app2

root@debian:~/proxyinverso# nano /etc/nginx/sites-available/app1

server <span class="o">{</span>
        listen 80<span class="p">;</span>
        listen <span class="o">[</span>::]:80<span class="p">;</span>

        index index.html index.htm index.nginx-debian.html<span class="p">;</span>

        server_name www.app1.org<span class="p">;</span>

        location / <span class="o">{</span>
                proxy_pass http://localhost:8081<span class="p">;</span>
        <span class="o">}</span>
<span class="o">}</span>

root@debian:~/proxyinverso# nano /etc/nginx/sites-available/app2

server <span class="o">{</span>
        listen 80<span class="p">;</span>
        listen <span class="o">[</span>::]:80<span class="p">;</span>

        index index.html index.htm index.nginx-debian.html<span class="p">;</span>

        server_name www.app2.org<span class="p">;</span>

        location / <span class="o">{</span>
                proxy_pass http://localhost:8082<span class="p">;</span>
        <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Habilitamos dichos VirtualHost creando los correspondientes enlaces simbólicos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# <span class="nb">ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/app1 /etc/nginx/sites-enabled/
root@debian:~/proxyinverso# <span class="nb">ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/app2 /etc/nginx/sites-enabled/

root@debian:~/proxyinverso# systemctl reload nginx</code></pre></figure>

<p>Modificamos el fichero de resolución estática de nombres <strong>/etc/hosts</strong> para poder acceder a través de las cabeceras de las peticiones HTTP a los VirtualHost:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# nano /etc/hosts

127.0.0.1       localhost
127.0.1.1       debian
127.0.0.1       www.app1.org
127.0.0.1       www.app2.org

<span class="c"># The following lines are desirable for IPv6 capable hosts
</span>
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters</code></pre></figure>

<p>Si tratamos de acceder ahora a <strong>www.app1.org</strong> y <strong>www.app2.org</strong>, respectivamente, podremos apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/1q0TqGN/Captura-de-pantalla-de-2021-02-26-15-51-46.png" alt="nginx3" title="Joomla" /></p>

<p><img src="https://i.ibb.co/BTVts8D/Captura-de-pantalla-de-2021-02-26-15-53-17.png" alt="nginx4" title="Nextcloud" /></p>

<p>Ahora, en lugar de utilizar dos VirtualHost (uno para cada aplicación), vamos a utilizar el mismo para ambas (<strong>www.servidor.org</strong>), diferenciándolas mediante la ruta que solicitamos tras la <strong>/</strong>, de manera que es hora de generar un nuevo VirtualHost con dichas características:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# <span class="nb">cp</span> /etc/nginx/sites-available/app1 /etc/nginx/sites-available/servidor

root@debian:~/proxyinverso# nano /etc/nginx/sites-available/servidor

server <span class="o">{</span>
        listen 80<span class="p">;</span>
        listen <span class="o">[</span>::]:80<span class="p">;</span>

        index index.html index.htm index.nginx-debian.html<span class="p">;</span>

        server_name www.servidor.org<span class="p">;</span>

        location /app1/ <span class="o">{</span>
                proxy_pass http://localhost:8081/<span class="p">;</span>
        <span class="o">}</span>

        location /app2/ <span class="o">{</span>
                proxy_pass http://localhost:8082/<span class="p">;</span>
        <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Habilitamos el VirtualHost creando el correspondiente enlace simbólico:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# <span class="nb">ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/servidor /etc/nginx/sites-enabled/

root@debian:~/proxyinverso# systemctl reload nginx</code></pre></figure>

<p>Modificamos el fichero de resolución estática de nombres <strong>/etc/hosts</strong> para poder acceder a través de las cabeceras de las peticiones HTTP al VirtualHost:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~/proxyinverso# nano /etc/hosts

127.0.0.1       localhost
127.0.1.1       debian
127.0.0.1       www.app1.org
127.0.0.1       www.app2.org
127.0.0.1       www.servidor.org

<span class="c"># The following lines are desirable for IPv6 capable hosts
</span>
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters</code></pre></figure>

<p>Si tratamos de acceder ahora a <strong>www.servidor.org/app1</strong> y <strong>www.servidor.org/app2</strong>, respectivamente, podremos apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/kyvk8q7/Captura-de-pantalla-de-2021-03-02-19-17-29.png" alt="nginx5" title="Joomla" /></p>

<p><img src="https://i.ibb.co/QD1C5DD/Captura-de-pantalla-de-2021-03-02-19-17-36.png" alt="nginx6" title="Nextcloud" /></p>

<p>El proxy inverso ha funcionado correctamente, sin embargo, al estar haciendo uso de Docker y de rutas virtuales para acceder a las aplicaciones, el contenido estático no se está sirviendo como debería, ya que no es capaz de encontrarlo en la ruta especificada.</p>

<p>Existen determinadas aplicaciones que vienen preparadas para, mediante variables de entorno, indicarles desde dónde se pretende acceder a las mismas, y por tanto, permiten servir el contenido estático sin mayor complicación, pero este no es el caso.</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="servicios" /><summary type="html"><![CDATA[Instalamos docker.io, docker-compose y nginx: root@debian:~# apt update &amp;&amp; apt install docker.io docker-compose nginx Creamos un fichero de nombre docker-compose.yaml en el que definimos los contenedores que existirán en el escenario, dos para la aplicación joomla y otros dos para nextcloud (base de datos y aplicación): root@debian:~/proxyinverso# nano docker-compose.yaml version: '3.1' services: joomla: container_name: servidor_joomla image: joomla restart: always environment: JOOMLA_DB_HOST: dbjoomla JOOMLA_DB_USER: user_joomla JOOMLA_DB_PASSWORD: asdasd JOOMLA_DB_NAME: bd_joomla ports: - 8081:80 dbjoomla: container_name: db_joomla image: mariadb restart: always environment: MYSQL_DATABASE: bd_joomla MYSQL_USER: user_joomla MYSQL_PASSWORD: asdasd MYSQL_ROOT_PASSWORD: asdasd nextcloud: container_name: servidor_nextcloud image: nextcloud restart: always environment: MYSQL_HOST: dbnextcloud MYSQL_USER: user_nextcloud MYSQL_PASSWORD: asdasd MYSQL_DATABASE: bd_nextcloud ports: - 8082:80 dbnextcloud: container_name: db_nextcloud image: mariadb restart: always environment: MYSQL_DATABASE: bd_nextcloud MYSQL_USER: user_nextcloud MYSQL_PASSWORD: asdasd MYSQL_ROOT_PASSWORD: asdasd Levantamos el escenario que acabamos de definir: root@debian:~/proxyinverso# docker-compose up -d Creating servidor_nextcloud ... done Creating db_nextcloud ... done Creating db_joomla ... done Creating servidor_joomla ... done Comprobamos que las aplicaciones se están sirviendo en los puertos 8081 y 8082 respectivamente, sin hacer todavía ningún proxy inverso: Una vez que son accesibles, es hora de generar dos nuevos VirtualHost en los que haremos proxy inverso desde dos nombres de dominio distintos para acceder a dichas aplicaciones (www.app1.org y www.app2.org): root@debian:~/proxyinverso# cp /etc/nginx/sites-available/default /etc/nginx/sites-available/app1 root@debian:~/proxyinverso# cp /etc/nginx/sites-available/default /etc/nginx/sites-available/app2 root@debian:~/proxyinverso# nano /etc/nginx/sites-available/app1 server { listen 80; listen [::]:80; index index.html index.htm index.nginx-debian.html; server_name www.app1.org; location / { proxy_pass http://localhost:8081; } } root@debian:~/proxyinverso# nano /etc/nginx/sites-available/app2 server { listen 80; listen [::]:80; index index.html index.htm index.nginx-debian.html; server_name www.app2.org; location / { proxy_pass http://localhost:8082; } } Habilitamos dichos VirtualHost creando los correspondientes enlaces simbólicos: root@debian:~/proxyinverso# ln -s /etc/nginx/sites-available/app1 /etc/nginx/sites-enabled/ root@debian:~/proxyinverso# ln -s /etc/nginx/sites-available/app2 /etc/nginx/sites-enabled/ root@debian:~/proxyinverso# systemctl reload nginx Modificamos el fichero de resolución estática de nombres /etc/hosts para poder acceder a través de las cabeceras de las peticiones HTTP a los VirtualHost: root@debian:~/proxyinverso# nano /etc/hosts 127.0.0.1 localhost 127.0.1.1 debian 127.0.0.1 www.app1.org 127.0.0.1 www.app2.org # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters Si tratamos de acceder ahora a www.app1.org y www.app2.org, respectivamente, podremos apreciar lo siguiente: Ahora, en lugar de utilizar dos VirtualHost (uno para cada aplicación), vamos a utilizar el mismo para ambas (www.servidor.org), diferenciándolas mediante la ruta que solicitamos tras la /, de manera que es hora de generar un nuevo VirtualHost con dichas características: root@debian:~/proxyinverso# cp /etc/nginx/sites-available/app1 /etc/nginx/sites-available/servidor root@debian:~/proxyinverso# nano /etc/nginx/sites-available/servidor server { listen 80; listen [::]:80; index index.html index.htm index.nginx-debian.html; server_name www.servidor.org; location /app1/ { proxy_pass http://localhost:8081/; } location /app2/ { proxy_pass http://localhost:8082/; } } Habilitamos el VirtualHost creando el correspondiente enlace simbólico: root@debian:~/proxyinverso# ln -s /etc/nginx/sites-available/servidor /etc/nginx/sites-enabled/ root@debian:~/proxyinverso# systemctl reload nginx Modificamos el fichero de resolución estática de nombres /etc/hosts para poder acceder a través de las cabeceras de las peticiones HTTP al VirtualHost: root@debian:~/proxyinverso# nano /etc/hosts 127.0.0.1 localhost 127.0.1.1 debian 127.0.0.1 www.app1.org 127.0.0.1 www.app2.org 127.0.0.1 www.servidor.org # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters Si tratamos de acceder ahora a www.servidor.org/app1 y www.servidor.org/app2, respectivamente, podremos apreciar lo siguiente: El proxy inverso ha funcionado correctamente, sin embargo, al estar haciendo uso de Docker y de rutas virtuales para acceder a las aplicaciones, el contenido estático no se está sirviendo como debería, ya que no es capaz de encontrarlo en la ruta especificada. Existen determinadas aplicaciones que vienen preparadas para, mediante variables de entorno, indicarles desde dónde se pretende acceder a las mismas, y por tanto, permiten servir el contenido estático sin mayor complicación, pero este no es el caso.]]></summary></entry><entry><title type="html">VPN con OpenVPN y certificados x509</title><link href="https://www.alvarovf.com/seguridad/2021/03/01/vpn-openvpn-x509.html" rel="alternate" type="text/html" title="VPN con OpenVPN y certificados x509" /><published>2021-03-01T06:59:00+00:00</published><updated>2021-03-01T06:59:00+00:00</updated><id>https://www.alvarovf.com/seguridad/2021/03/01/vpn-openvpn-x509</id><content type="html" xml:base="https://www.alvarovf.com/seguridad/2021/03/01/vpn-openvpn-x509.html"><![CDATA[<h2 id="tarea-1-vpn-de-acceso-remoto-con-openvpn-y-certificados-x509">Tarea 1: VPN de acceso remoto con OpenVPN y certificados x509</h2>

<p><img src="https://i.ibb.co/ZhCcWRK/Captura-de-pantalla-de-2021-03-01-10-19-11.png" alt="vpn1" title="VPN Cliente-Servidor" /></p>

<p><strong>Servidor</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">dev tun <span class="c">#Dispositivo de túnel
</span>
    
server 10.99.99.0 255.255.255.0 <span class="c">#Direcciones IP virtuales
</span>

push <span class="s2">"route 10.10.10.0 255.255.255.0"</span> <span class="c">#Enviamos la subred local
</span>

tls-server <span class="c">#Rol de servidor
</span>

dh /etc/openvpn/keys/dh.pem <span class="c">#Parámetros Diffie-Hellman, que permite acordar una clave secreta entre dos máquinas, a través de un canal inseguro y enviando únicamente dos mensajes.
</span>

ca /etc/openvpn/keys/ca.crt <span class="c">#Certificado de la CA
</span>

cert /etc/openvpn/keys/server.crt <span class="c">#Certificado local (servidor)
</span>

key /etc/openvpn/keys/server.key <span class="c">#Clave privada local (servidor)
</span>

comp-lzo <span class="c">#Activar la compresión LZO
</span>

keepalive 10 60 <span class="c">#Detectar caídas de la conexión
</span>

log /var/log/openvpn/server.log <span class="c">#Fichero para los logs
</span>

askpass pass.txt <span class="c">#Fichero con la frase de paso de la clave privada, para que no la pida de forma interactiva
</span>

verb 3 <span class="c">#Nivel de debug</span></code></pre></figure>

<p><strong>Cliente</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">dev tun

remote 192.168.1.136 <span class="c">#IP del servidor VPN
</span>

pull <span class="c">#Pedimos las rutas al servidor VPN
</span>

tls-client <span class="c">#Rol de cliente
</span>

remote-cert-tls server <span class="c">#Nos autenticamos ante el servidor con certificados
</span>

ca /etc/openvpn/keys/ca.crt

cert /etc/openvpn/keys/cliente1.crt <span class="c">#Certificado local (cliente)
</span>

key /etc/openvpn/keys/cliente1.key <span class="c">#Clave privada local (cliente)
</span>

comp-lzo

keepalive 10 60

log /var/log/openvpn/cliente.log

askpass pass.txt

verb 3</code></pre></figure>

<h2 id="tarea-2-vpn-sitio-a-sitio-con-openvpn-y-certificados-x509">Tarea 2: VPN sitio a sitio con OpenVPN y certificados x509</h2>

<p><img src="https://i.ibb.co/mTyGQxB/Captura-de-pantalla-de-2021-03-02-12-43-00.png" alt="vpn2" title="VPN Site-to-site" /></p>

<p><img src="https://i.ibb.co/Vtt3JDm/Captura-de-pantalla-de-2021-03-02-12-44-07.png" alt="vpn3" title="VPN Site-to-site" /></p>

<p><strong>Si actúas como servidor</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">dev tun

ifconfig 10.99.99.1 10.99.99.2

route 10.0.5.0 255.255.255.0

tls-server

dh /etc/openvpn/keys/dhalvaro.pem

ca /etc/openvpn/keys/caalvaro.crt

cert /etc/openvpn/keys/serveralvaro.crt

key /etc/openvpn/keys/serveralvaro.key

comp-lzo

keepalive 10 60

log /var/log/openvpn/prueba.log

askpass pass1.txt

verb 3</code></pre></figure>

<p><strong>Si actúas como cliente</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">dev tun

remote 172.22.200.253

ifconfig 10.99.99.2 10.99.99.1

route 10.0.5.0 255.255.255.0

tls-client

ca /etc/openvpn/keys/caJavier.crt

cert /etc/openvpn/keys/vpnAlvaro.crt

key /etc/openvpn/keys/vpnAlvaro.key

comp-lzo

keepalive 10 60

log /var/log/openvpn/cliente.log

askpass pass2.txt

verb 3</code></pre></figure>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="seguridad" /><summary type="html"><![CDATA[Tarea 1: VPN de acceso remoto con OpenVPN y certificados x509 Servidor: dev tun #Dispositivo de túnel server 10.99.99.0 255.255.255.0 #Direcciones IP virtuales push "route 10.10.10.0 255.255.255.0" #Enviamos la subred local tls-server #Rol de servidor dh /etc/openvpn/keys/dh.pem #Parámetros Diffie-Hellman, que permite acordar una clave secreta entre dos máquinas, a través de un canal inseguro y enviando únicamente dos mensajes. ca /etc/openvpn/keys/ca.crt #Certificado de la CA cert /etc/openvpn/keys/server.crt #Certificado local (servidor) key /etc/openvpn/keys/server.key #Clave privada local (servidor) comp-lzo #Activar la compresión LZO keepalive 10 60 #Detectar caídas de la conexión log /var/log/openvpn/server.log #Fichero para los logs askpass pass.txt #Fichero con la frase de paso de la clave privada, para que no la pida de forma interactiva verb 3 #Nivel de debug Cliente: dev tun remote 192.168.1.136 #IP del servidor VPN pull #Pedimos las rutas al servidor VPN tls-client #Rol de cliente remote-cert-tls server #Nos autenticamos ante el servidor con certificados ca /etc/openvpn/keys/ca.crt cert /etc/openvpn/keys/cliente1.crt #Certificado local (cliente) key /etc/openvpn/keys/cliente1.key #Clave privada local (cliente) comp-lzo keepalive 10 60 log /var/log/openvpn/cliente.log askpass pass.txt verb 3 Tarea 2: VPN sitio a sitio con OpenVPN y certificados x509 Si actúas como servidor: dev tun ifconfig 10.99.99.1 10.99.99.2 route 10.0.5.0 255.255.255.0 tls-server dh /etc/openvpn/keys/dhalvaro.pem ca /etc/openvpn/keys/caalvaro.crt cert /etc/openvpn/keys/serveralvaro.crt key /etc/openvpn/keys/serveralvaro.key comp-lzo keepalive 10 60 log /var/log/openvpn/prueba.log askpass pass1.txt verb 3 Si actúas como cliente: dev tun remote 172.22.200.253 ifconfig 10.99.99.2 10.99.99.1 route 10.0.5.0 255.255.255.0 tls-client ca /etc/openvpn/keys/caJavier.crt cert /etc/openvpn/keys/vpnAlvaro.crt key /etc/openvpn/keys/vpnAlvaro.key comp-lzo keepalive 10 60 log /var/log/openvpn/cliente.log askpass pass2.txt verb 3]]></summary></entry><entry><title type="html">Instalación de un proxy squid</title><link href="https://www.alvarovf.com/servicios/2021/02/28/instalacion-proxy-squid.html" rel="alternate" type="text/html" title="Instalación de un proxy squid" /><published>2021-02-28T13:15:00+00:00</published><updated>2021-02-28T13:15:00+00:00</updated><id>https://www.alvarovf.com/servicios/2021/02/28/instalacion-proxy-squid</id><content type="html" xml:base="https://www.alvarovf.com/servicios/2021/02/28/instalacion-proxy-squid.html"><![CDATA[<p>Instalamos <strong>squid</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# apt update <span class="o">&amp;&amp;</span> apt upgrade <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>squid</code></pre></figure>

<p>Modificamos la configuración del proxy, en la que establecemos las redes y puertos que queremos permitir, así como el puerto de funcionamiento:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# nano /etc/squid/squid.conf

acl localnet src 10.0.0.0/24
acl localnet src 192.168.200.0/24

acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl Safe_ports port 21
acl CONNECT method CONNECT

http_access deny <span class="o">!</span>Safe_ports
http_access deny CONNECT <span class="o">!</span>SSL_ports
http_access allow localhost manager
http_access deny manager

http_access allow localnet
http_access allow localhost

http_access deny all

http_port 3128

coredump_dir /var/spool/squid

root@proxy:~# systemctl restart squid</code></pre></figure>

<p>Accedemos a la configuración de proxy del navegador y lo configuramos de manera que haga uso de la máquina servidora, para así poder controlar el tráfico desde la misma:</p>

<p><img src="https://i.ibb.co/zNkCpxf/Captura-de-pantalla-de-2021-02-26-11-38-32.png" alt="squid1" title="Configuración navegador" /></p>

<p>Tratamos de acceder a cualquier página para verificar que el proxy está funcionando y es accesible desde la máquina anfitriona:</p>

<p><img src="https://i.ibb.co/RHZQQcF/Captura-de-pantalla-de-2021-02-26-11-48-06.png" alt="squid2" title="Prueba 1" /></p>

<p>Comprobamos los logs en la máquina squid para así verificar que dicha conexión ha dejado “rastro”:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# <span class="nb">cat</span> /var/log/squid/access.log
1614336540.269  57635 192.168.200.1 TCP_TUNNEL/200 102191 CONNECT translate.googleapis.com:443 - HIER_DIRECT/216.58.215.138 -
1614336540.269  58196 192.168.200.1 TCP_TUNNEL/200 4433 CONNECT cdn.jsdelivr.net:443 - HIER_DIRECT/104.16.86.20 -
1614336540.269  58189 192.168.200.1 TCP_TUNNEL/200 7537 CONNECT translate.google.com:443 - HIER_DIRECT/216.58.209.78 -
1614336540.269  58023 192.168.200.1 TCP_TUNNEL/200 5801 CONNECT www.countryflags.io:443 - HIER_DIRECT/172.67.135.78 -
1614336540.271  58201 192.168.200.1 TCP_TUNNEL/200 3407 CONNECT cdnjs.cloudflare.com:443 - HIER_DIRECT/104.16.19.94 -
1614336540.271  60721 192.168.200.1 TCP_TUNNEL/200 705030 CONNECT www.alvarovf.com:443 - HIER_DIRECT/151.101.133.0 -
1614336540.922    128 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response
1614336540.929    130 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response
1614336540.946    194 192.168.200.1 TCP_TUNNEL/200 4692 CONNECT www.gstatic.com:443 - HIER_DIRECT/142.250.185.3 -
1614336540.957    134 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response
1614336540.975    200 192.168.200.1 TCP_TUNNEL/200 5630 CONNECT www.gstatic.com:443 - HIER_DIRECT/142.250.185.3 -</code></pre></figure>

<p>Tras ello, revertimos los cambios en el navegador para que vuelva a hacer uso del proxy según la configuración del sistema, para ahora proceder a configurar dicho proxy de forma general en el sistema, quedando de la siguiente forma:</p>

<p><img src="https://i.ibb.co/s1cmYvW/Captura-de-pantalla-de-2021-02-26-11-51-08.png" alt="squid3" title="Configuración sistema" /></p>

<p>Volvemos a acceder a otra página para verificar que la configuración del proxy en el sistema funciona:</p>

<p><img src="https://i.ibb.co/tX7ZrFW/Captura-de-pantalla-de-2021-02-26-11-51-24.png" alt="squid4" title="Configuración sistema" /></p>

<p>Comprobamos los logs en la máquina squid para así verificar que dicha conexión ha dejado “rastro”:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# <span class="nb">cat</span> /var/log/squid/access.log
1614336680.067    134 192.168.200.1 TCP_MISS/301 643 GET http://fp.josedomingo.org/ - HIER_DIRECT/37.187.119.60 text/html
1614336680.228     42 192.168.200.1 TCP_MISS/200 981 POST http://r3.o.lencr.org/ - HIER_DIRECT/212.230.153.18 application/ocsp-response
1614336685.463      0 192.168.200.1 NONE/000 0 NONE error:transaction-end-before-headers - HIER_NONE/- -
1614336686.465   6104 192.168.200.1 TCP_TUNNEL/200 1787 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 -
1614336686.465   6104 192.168.200.1 TCP_TUNNEL/200 41333 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 -
1614336686.465   6105 192.168.200.1 TCP_TUNNEL/200 9339 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 -
1614336686.465   6106 192.168.200.1 TCP_TUNNEL/200 43692 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 -
1614336686.466   6392 192.168.200.1 TCP_TUNNEL/200 26863 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 -</code></pre></figure>

<p>Desde una máquina conectada a una red interna (sin salida al exterior mediante NAT), vamos a realizar determinadas pruebas, no sin antes establecer la correspondiente variable de entorno para que la máquina utilice el servidor proxy en cuestión:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@buster:~# <span class="nb">export </span><span class="nv">http_proxy</span><span class="o">=</span><span class="s1">'http://10.0.0.10:3128'</span></code></pre></figure>

<p>Verificamos que tenemos salida al exterior a través de dicho proxy descargando el index.html de google:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@buster:~# wget www.google.es
<span class="nt">--2021-02-26</span> 11:08:45--  http://www.google.es/
Connecting to 10.0.0.10:3128... connected.
Proxy request sent, awaiting response... 200 OK
Length: unspecified <span class="o">[</span>text/html]
Saving to: ‘index.html’

index.html                                               <span class="o">[</span> &lt;<span class="o">=&gt;</span>                                                                                                                  <span class="o">]</span>  14.33K  <span class="nt">--</span>.-KB/s    <span class="k">in </span>0s      

2021-02-26 11:08:45 <span class="o">(</span>63.8 MB/s<span class="o">)</span> - ‘index.html’ saved <span class="o">[</span>14675]</code></pre></figure>

<p>Pero sin embargo, no podemos hacerle ping:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@buster:~# ping www.google.es
PING www.google.es <span class="o">(</span>216.58.211.227<span class="o">)</span> 56<span class="o">(</span>84<span class="o">)</span> bytes of data.
^C
<span class="nt">---</span> www.google.es ping statistics <span class="nt">---</span>
7 packets transmitted, 0 received, 100% packet loss, <span class="nb">time </span>253ms</code></pre></figure>

<p>Vamos a añadir una lista negra a squid para evitar el tráfico hacia dichas páginas, para ello modificamos la configuración del servicio, quedando de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# nano /etc/squid/squid.conf

acl localnet src 10.0.0.0/24
acl localnet src 192.168.200.0/24

acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl Safe_ports port 21
acl CONNECT method CONNECT

acl domain_blacklist dstdomain <span class="s2">"/etc/squid/domain_blacklist.txt"</span>

http_access deny <span class="o">!</span>Safe_ports
http_access deny CONNECT <span class="o">!</span>SSL_ports
http_access allow localhost manager
http_access deny manager

http_access deny domain_blacklist

http_access allow localnet
http_access allow localhost

http_access deny all

http_port 3128

coredump_dir /var/spool/squid</code></pre></figure>

<p>Dentro de dicha blacklist ponemos un sitio de ejemplo, como twitter.com y todos sus subdominios:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# nano /etc/squid/domain_blacklist.txt

.twitter.com

root@proxy:~# systemctl restart squid</code></pre></figure>

<p>Si tratamos de acceder ahora a twitter.com ocurrirá lo siguiente:</p>

<p><img src="https://i.ibb.co/g38ZzTT/Captura-de-pantalla-de-2021-02-26-12-16-30.png" alt="squid4" title="Prueba 2" /></p>

<p>En los logs, podremos apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# <span class="nb">cat</span> /var/log/squid/access.log

1614338187.184      0 192.168.200.1 TCP_DENIED/403 3953 CONNECT twitter.com:443 - HIER_NONE/- text/html</code></pre></figure>

<p>Ahora, en lugar de configurar una blacklist, vamos a configurar una whitelist (solo se permiten las páginas indicadas explícitamente), quedando el fichero de configuración de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# nano /etc/squid/squid.conf

acl localnet src 10.0.0.0/24
acl localnet src 192.168.200.0/24

acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl Safe_ports port 21
acl CONNECT method CONNECT

acl domain_whitelist dstdomain <span class="s2">"/etc/squid/domain_whitelist.txt"</span>

http_access deny <span class="o">!</span>Safe_ports
http_access deny CONNECT <span class="o">!</span>SSL_ports
http_access allow localhost manager
http_access deny manager

http_access deny <span class="o">!</span>domain_whitelist

http_access allow localnet
http_access allow localhost

http_access deny all

http_port 3128

coredump_dir /var/spool/squid</code></pre></figure>

<p>Dentro de dicha whitelist ponemos un sitio de ejemplo, como alvarovf.com y todos sus subdominios:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# nano /etc/squid/domain_whitelist.txt

.alvarovf.com

root@proxy:~# systemctl restart squid</code></pre></figure>

<p>Si tratamos de acceder ahora a google.com ocurrirá lo siguiente:</p>

<p><img src="https://i.ibb.co/dPBP242/Captura-de-pantalla-de-2021-02-26-12-23-26.png" alt="squid5" title="Prueba 3" /></p>

<p>Sin embargo, en alvarovf.com:</p>

<p><img src="https://i.ibb.co/DtQXqTr/Captura-de-pantalla-de-2021-02-26-12-24-46.png" alt="squid6" title="Prueba 4" /></p>

<p>En los logs, podremos apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@proxy:~# <span class="nb">cat</span> /var/log/squid/access.log 
1614338729.105      0 192.168.200.1 TCP_DENIED/403 3962 CONNECT www.google.com:443 - HIER_NONE/- text/html
1614338754.926  56656 192.168.200.1 TCP_TUNNEL/200 702338 CONNECT www.alvarovf.com:443 - HIER_DIRECT/151.101.133.0 -
1614338755.060      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html
1614338755.061      0 192.168.200.1 TCP_DENIED/403 3968 CONNECT cdn.jsdelivr.net:443 - HIER_NONE/- text/html
1614338755.061      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html
1614338755.061      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html
1614338755.061      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html
1614338755.062      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.063      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.064      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.064      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.065      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.066      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT translate.google.com:443 - HIER_NONE/- text/html
1614338755.069      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.069      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.086      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.087      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.088      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.089      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.090      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.090      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.090      0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html
1614338755.091      0 192.168.200.1 TCP_DENIED/403 3980 CONNECT translate.google.com:443 - HIER_NONE/- text/html</code></pre></figure>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="servicios" /><summary type="html"><![CDATA[Instalamos squid: root@proxy:~# apt update &amp;&amp; apt upgrade &amp;&amp; apt install squid Modificamos la configuración del proxy, en la que establecemos las redes y puertos que queremos permitir, así como el puerto de funcionamiento: root@proxy:~# nano /etc/squid/squid.conf acl localnet src 10.0.0.0/24 acl localnet src 192.168.200.0/24 acl SSL_ports port 443 acl Safe_ports port 80 acl Safe_ports port 443 acl Safe_ports port 21 acl CONNECT method CONNECT http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access allow localnet http_access allow localhost http_access deny all http_port 3128 coredump_dir /var/spool/squid root@proxy:~# systemctl restart squid Accedemos a la configuración de proxy del navegador y lo configuramos de manera que haga uso de la máquina servidora, para así poder controlar el tráfico desde la misma: Tratamos de acceder a cualquier página para verificar que el proxy está funcionando y es accesible desde la máquina anfitriona: Comprobamos los logs en la máquina squid para así verificar que dicha conexión ha dejado “rastro”: root@proxy:~# cat /var/log/squid/access.log 1614336540.269 57635 192.168.200.1 TCP_TUNNEL/200 102191 CONNECT translate.googleapis.com:443 - HIER_DIRECT/216.58.215.138 - 1614336540.269 58196 192.168.200.1 TCP_TUNNEL/200 4433 CONNECT cdn.jsdelivr.net:443 - HIER_DIRECT/104.16.86.20 - 1614336540.269 58189 192.168.200.1 TCP_TUNNEL/200 7537 CONNECT translate.google.com:443 - HIER_DIRECT/216.58.209.78 - 1614336540.269 58023 192.168.200.1 TCP_TUNNEL/200 5801 CONNECT www.countryflags.io:443 - HIER_DIRECT/172.67.135.78 - 1614336540.271 58201 192.168.200.1 TCP_TUNNEL/200 3407 CONNECT cdnjs.cloudflare.com:443 - HIER_DIRECT/104.16.19.94 - 1614336540.271 60721 192.168.200.1 TCP_TUNNEL/200 705030 CONNECT www.alvarovf.com:443 - HIER_DIRECT/151.101.133.0 - 1614336540.922 128 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response 1614336540.929 130 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response 1614336540.946 194 192.168.200.1 TCP_TUNNEL/200 4692 CONNECT www.gstatic.com:443 - HIER_DIRECT/142.250.185.3 - 1614336540.957 134 192.168.200.1 TCP_MISS/200 818 POST http://ocsp.pki.goog/gts1o1core - HIER_DIRECT/216.58.211.227 application/ocsp-response 1614336540.975 200 192.168.200.1 TCP_TUNNEL/200 5630 CONNECT www.gstatic.com:443 - HIER_DIRECT/142.250.185.3 - Tras ello, revertimos los cambios en el navegador para que vuelva a hacer uso del proxy según la configuración del sistema, para ahora proceder a configurar dicho proxy de forma general en el sistema, quedando de la siguiente forma: Volvemos a acceder a otra página para verificar que la configuración del proxy en el sistema funciona: Comprobamos los logs en la máquina squid para así verificar que dicha conexión ha dejado “rastro”: root@proxy:~# cat /var/log/squid/access.log 1614336680.067 134 192.168.200.1 TCP_MISS/301 643 GET http://fp.josedomingo.org/ - HIER_DIRECT/37.187.119.60 text/html 1614336680.228 42 192.168.200.1 TCP_MISS/200 981 POST http://r3.o.lencr.org/ - HIER_DIRECT/212.230.153.18 application/ocsp-response 1614336685.463 0 192.168.200.1 NONE/000 0 NONE error:transaction-end-before-headers - HIER_NONE/- - 1614336686.465 6104 192.168.200.1 TCP_TUNNEL/200 1787 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 - 1614336686.465 6104 192.168.200.1 TCP_TUNNEL/200 41333 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 - 1614336686.465 6105 192.168.200.1 TCP_TUNNEL/200 9339 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 - 1614336686.465 6106 192.168.200.1 TCP_TUNNEL/200 43692 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 - 1614336686.466 6392 192.168.200.1 TCP_TUNNEL/200 26863 CONNECT fp.josedomingo.org:443 - HIER_DIRECT/37.187.119.60 - Desde una máquina conectada a una red interna (sin salida al exterior mediante NAT), vamos a realizar determinadas pruebas, no sin antes establecer la correspondiente variable de entorno para que la máquina utilice el servidor proxy en cuestión: root@buster:~# export http_proxy='http://10.0.0.10:3128' Verificamos que tenemos salida al exterior a través de dicho proxy descargando el index.html de google: root@buster:~# wget www.google.es --2021-02-26 11:08:45-- http://www.google.es/ Connecting to 10.0.0.10:3128... connected. Proxy request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: ‘index.html’ index.html [ &lt;=&gt; ] 14.33K --.-KB/s in 0s 2021-02-26 11:08:45 (63.8 MB/s) - ‘index.html’ saved [14675] Pero sin embargo, no podemos hacerle ping: root@buster:~# ping www.google.es PING www.google.es (216.58.211.227) 56(84) bytes of data. ^C --- www.google.es ping statistics --- 7 packets transmitted, 0 received, 100% packet loss, time 253ms Vamos a añadir una lista negra a squid para evitar el tráfico hacia dichas páginas, para ello modificamos la configuración del servicio, quedando de la siguiente forma: root@proxy:~# nano /etc/squid/squid.conf acl localnet src 10.0.0.0/24 acl localnet src 192.168.200.0/24 acl SSL_ports port 443 acl Safe_ports port 80 acl Safe_ports port 443 acl Safe_ports port 21 acl CONNECT method CONNECT acl domain_blacklist dstdomain "/etc/squid/domain_blacklist.txt" http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access deny domain_blacklist http_access allow localnet http_access allow localhost http_access deny all http_port 3128 coredump_dir /var/spool/squid Dentro de dicha blacklist ponemos un sitio de ejemplo, como twitter.com y todos sus subdominios: root@proxy:~# nano /etc/squid/domain_blacklist.txt .twitter.com root@proxy:~# systemctl restart squid Si tratamos de acceder ahora a twitter.com ocurrirá lo siguiente: En los logs, podremos apreciar lo siguiente: root@proxy:~# cat /var/log/squid/access.log 1614338187.184 0 192.168.200.1 TCP_DENIED/403 3953 CONNECT twitter.com:443 - HIER_NONE/- text/html Ahora, en lugar de configurar una blacklist, vamos a configurar una whitelist (solo se permiten las páginas indicadas explícitamente), quedando el fichero de configuración de la siguiente forma: root@proxy:~# nano /etc/squid/squid.conf acl localnet src 10.0.0.0/24 acl localnet src 192.168.200.0/24 acl SSL_ports port 443 acl Safe_ports port 80 acl Safe_ports port 443 acl Safe_ports port 21 acl CONNECT method CONNECT acl domain_whitelist dstdomain "/etc/squid/domain_whitelist.txt" http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access deny !domain_whitelist http_access allow localnet http_access allow localhost http_access deny all http_port 3128 coredump_dir /var/spool/squid Dentro de dicha whitelist ponemos un sitio de ejemplo, como alvarovf.com y todos sus subdominios: root@proxy:~# nano /etc/squid/domain_whitelist.txt .alvarovf.com root@proxy:~# systemctl restart squid Si tratamos de acceder ahora a google.com ocurrirá lo siguiente: Sin embargo, en alvarovf.com: En los logs, podremos apreciar lo siguiente: root@proxy:~# cat /var/log/squid/access.log 1614338729.105 0 192.168.200.1 TCP_DENIED/403 3962 CONNECT www.google.com:443 - HIER_NONE/- text/html 1614338754.926 56656 192.168.200.1 TCP_TUNNEL/200 702338 CONNECT www.alvarovf.com:443 - HIER_DIRECT/151.101.133.0 - 1614338755.060 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html 1614338755.061 0 192.168.200.1 TCP_DENIED/403 3968 CONNECT cdn.jsdelivr.net:443 - HIER_NONE/- text/html 1614338755.061 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html 1614338755.061 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html 1614338755.061 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT cdnjs.cloudflare.com:443 - HIER_NONE/- text/html 1614338755.062 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.063 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.064 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.064 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.065 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.066 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT translate.google.com:443 - HIER_NONE/- text/html 1614338755.069 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.069 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.086 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.087 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.088 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.089 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.090 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.090 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.090 0 192.168.200.1 TCP_DENIED/403 3977 CONNECT www.countryflags.io:443 - HIER_NONE/- text/html 1614338755.091 0 192.168.200.1 TCP_DENIED/403 3980 CONNECT translate.google.com:443 - HIER_NONE/- text/html]]></summary></entry><entry><title type="html">Aumentar el rendimiento de servidores web</title><link href="https://www.alvarovf.com/servicios/2021/02/18/aumentar-rendimiento-varnish.html" rel="alternate" type="text/html" title="Aumentar el rendimiento de servidores web" /><published>2021-02-18T07:01:00+00:00</published><updated>2021-02-18T07:01:00+00:00</updated><id>https://www.alvarovf.com/servicios/2021/02/18/aumentar-rendimiento-varnish</id><content type="html" xml:base="https://www.alvarovf.com/servicios/2021/02/18/aumentar-rendimiento-varnish.html"><![CDATA[<p>Instalamos <strong>ansible</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~<span class="nv">$ </span><span class="nb">sudo </span>apt update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>ansible</code></pre></figure>

<p>Clonamos el repositorio con la <strong>receta</strong> en su interior:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~/GitHub<span class="nv">$ </span>git clone https://github.com/josedom24/ansible_nginx_fpm_php.git
Clonando en <span class="s1">'ansible_nginx_fpm_php'</span>...
remote: Enumerating objects: 40, <span class="k">done</span><span class="nb">.</span>
remote: Counting objects: 100% <span class="o">(</span>40/40<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Compressing objects: 100% <span class="o">(</span>27/27<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Total 40 <span class="o">(</span>delta 0<span class="o">)</span>, reused 36 <span class="o">(</span>delta 0<span class="o">)</span>, pack-reused 0
Desempaquetando objetos: 100% <span class="o">(</span>40/40<span class="o">)</span>, listo.</code></pre></figure>

<p>Listamos el contenido del repositorio clonado:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~/GitHub/ansible_nginx_fpm_php<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 44
<span class="nt">-rw-r--r--</span> 1 alvaro alvaro    76 feb 10 18:00 ansible.cfg
drwxr-xr-x 2 alvaro alvaro  4096 feb 10 18:00 group_vars
<span class="nt">-rw-r--r--</span> 1 alvaro alvaro    98 feb 10 18:00 hosts
<span class="nt">-rw-r--r--</span> 1 alvaro alvaro 18092 feb 10 18:00 LICENSE
<span class="nt">-rw-r--r--</span> 1 alvaro alvaro    84 feb 10 18:00 README.md
drwxr-xr-x 6 alvaro alvaro  4096 feb 10 18:00 roles
<span class="nt">-rw-r--r--</span> 1 alvaro alvaro   264 feb 10 18:00 site.yaml</code></pre></figure>

<p>Editamos el fichero <strong>hosts</strong> y establecemos la dirección IP de la máquina que vamos a utilizar para las pruebas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~/GitHub/ansible_nginx_fpm_php<span class="nv">$ </span>nano hosts

<span class="o">[</span>servidores_web]
nodo1 <span class="nv">ansible_ssh_host</span><span class="o">=</span>192.168.1.136 <span class="nv">ansible_python_interpreter</span><span class="o">=</span>/usr/bin/python3</code></pre></figure>

<p>Ejecutamos el <strong>playbook</strong> de <strong>ansible</strong> para así realizar las modificaciones iniciales en la máquina que utilizaremos para las pruebas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">alvaro@debian:~/GitHub/ansible_nginx_fpm_php<span class="nv">$ </span>ansible-playbook site.yaml 

PLAY <span class="o">[</span>servidores_web] <span class="k">**********************************************************************************</span>

TASK <span class="o">[</span>Gathering Facts] <span class="k">*********************************************************************************</span>
ok: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>nginx : <span class="nb">install </span>nginx, php-fpm] <span class="k">******************************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>nginx : Copy info.php] <span class="k">***************************************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>nginx : Copy virtualhost default] <span class="k">****************************************************************</span>
changed: <span class="o">[</span>nodo1]

RUNNING HANDLER <span class="o">[</span>nginx : restart nginx] <span class="k">****************************************************************</span>
changed: <span class="o">[</span>nodo1]

PLAY <span class="o">[</span>servidores_web] <span class="k">**********************************************************************************</span>

TASK <span class="o">[</span>Gathering Facts] <span class="k">*********************************************************************************</span>
ok: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>mariadb : ensure mariadb is installed] <span class="k">***********************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>mariadb : ensure mariadb binds to internal interface] <span class="k">********************************************</span>
changed: <span class="o">[</span>nodo1]

RUNNING HANDLER <span class="o">[</span>mariadb : restart mariadb] <span class="k">************************************************************</span>
changed: <span class="o">[</span>nodo1]

PLAY <span class="o">[</span>servidores_web] <span class="k">**********************************************************************************</span>

TASK <span class="o">[</span>Gathering Facts] <span class="k">*********************************************************************************</span>
ok: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>wordpress : <span class="nb">install </span>unzip] <span class="k">***********************************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>wordpress : download wordpress] <span class="k">******************************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>wordpress : unzip wordpress] <span class="k">*********************************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>wordpress : create database wordpress] <span class="k">***********************************************************</span>
changed: <span class="o">[</span>nodo1]

TASK <span class="o">[</span>wordpress : create user mysql wordpress] <span class="k">*********************************************************</span>
changed: <span class="o">[</span>nodo1] <span class="o">=&gt;</span> <span class="o">(</span><span class="nv">item</span><span class="o">=</span>localhost<span class="o">)</span>

TASK <span class="o">[</span>wordpress : copy wp-config.php] <span class="k">******************************************************************</span>
changed: <span class="o">[</span>nodo1]

RUNNING HANDLER <span class="o">[</span>wordpress : restart nginx] <span class="k">************************************************************</span>
changed: <span class="o">[</span>nodo1]

PLAY RECAP <span class="k">*********************************************************************************************</span>
nodo1                      : <span class="nv">ok</span><span class="o">=</span>17   <span class="nv">changed</span><span class="o">=</span>14   <span class="nv">unreachable</span><span class="o">=</span>0    <span class="nv">failed</span><span class="o">=</span>0</code></pre></figure>

<p>Accedemos desde el navegador web a http://<strong>IP</strong>/wordpress y procedemos con la instalación del CMS, quedando finalmente así:</p>

<p><img src="https://i.ibb.co/B2VBgv1/Captura-de-pantalla-de-2021-02-10-18-43-42.png" alt="wordpress1" title="Wordpress" /></p>

<p>Nos conectamos a la máquina de pruebas e instalamos <strong>apache2-utils</strong>, que contiene el comando <strong>ab</strong> que utilizaremos para los benchmarks:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>apache2-utils</code></pre></figure>

<p>Realizamos diferentes pruebas de rendimiento, alternando entre diversos niveles de concurrencia, para conocer el número de peticiones que es capaz de responder. Entre cada prueba, reiniciamos el servicio para que los resultados no se vean afectados:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    164.29 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    164.80 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    163.25 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    153.69 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    154.68 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    157.09 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    11170.51 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    12488.91 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    11782.17 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    14262.15 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    14513.48 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    14268.24 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx</code></pre></figure>

<p>Una vez conocido los resultados, vamos a hacer uso de un proxy inverso caché conocido como <strong>varnish</strong>, que cacheará las respuestas del servidor web para así ofrecerlas de una forma mucho más rápida. Para que los clientes no noten diferencia, dicho proxy inverso se encontrará escuchando peticiones en el puerto 80, en lugar de <strong>nginx</strong>, de manera que modificaremos el VirtualHost para así cambiar el puerto de nginx a por ejemplo, el 8080:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@varnish:~# nano /etc/nginx/sites-available/default

listen 8080 default_server<span class="p">;</span>
listen <span class="o">[</span>::]:8080 default_server<span class="p">;</span>

root@varnish:~# systemctl restart nginx</code></pre></figure>

<p>Nos aseguramos que ahora está escuchando peticiones en el 8080:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@varnish:~# netstat <span class="nt">-tln</span>
Active Internet connections <span class="o">(</span>only servers<span class="o">)</span>
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:8080            0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:3306            0.0.0.0:<span class="k">*</span>               LISTEN     
tcp6       0      0 :::8080                 :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::22                   :::<span class="k">*</span>                    LISTEN</code></pre></figure>

<p>Instalamos <strong>varnish</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@varnish:~# apt <span class="nb">install </span>varnish</code></pre></figure>

<p>Realizamos las correspondientes modificaciones en los ficheros de configuración para que el servicio escuche en el puerto 80 y redirija las peticiones al puerto 8080 (nginx), en caso de ser necesario:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@varnish:~# nano /etc/varnish/default.vcl

backend default <span class="o">{</span>
    .host <span class="o">=</span> <span class="s2">"127.0.0.1"</span><span class="p">;</span>
    .port <span class="o">=</span> <span class="s2">"8080"</span><span class="p">;</span>
<span class="o">}</span>

root@varnish:~# nano /etc/default/varnish

<span class="nv">DAEMON_OPTS</span><span class="o">=</span><span class="s2">"-a :80 </span><span class="se">\
</span><span class="s2">
             -T localhost:6082 </span><span class="se">\
</span><span class="s2">
             -f /etc/varnish/default.vcl </span><span class="se">\
</span><span class="s2">
             -S /etc/varnish/secret </span><span class="se">\
</span><span class="s2">
             -s malloc,256m"</span>

root@varnish:~# nano /lib/systemd/system/varnish.service

<span class="nv">ExecStart</span><span class="o">=</span>/usr/sbin/varnishd <span class="nt">-j</span> unix,user<span class="o">=</span>vcache <span class="nt">-F</span> <span class="nt">-a</span> :80 <span class="nt">-T</span> localhost:6082 <span class="nt">-f</span> /etc/varnish/default.vcl <span class="nt">-S</span> /etc/varnish/secret <span class="nt">-s</span> malloc,256m

root@varnish:~# systemctl daemon-reload

root@varnish:~# systemctl restart varnish</code></pre></figure>

<p>Nos aseguramos que ahora está escuchando peticiones en el 80:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@varnish:~# netstat <span class="nt">-tln</span>
Active Internet connections <span class="o">(</span>only servers<span class="o">)</span>
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:80              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:8080            0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 127.0.0.1:6082          0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:3306            0.0.0.0:<span class="k">*</span>               LISTEN     
tcp6       0      0 :::80                   :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::8080                 :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::22                   :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 ::1:6082                :::<span class="k">*</span>                    LISTEN</code></pre></figure>

<p>Repetimos las pruebas anteriores para así verificar que el número de peticiones respondidas por segundo es ahora mucho mayor, al tener un proxy inverso caché por delante:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    37704.80 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    38217.53 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 50 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    37134.25 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    37765.69 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    36442.75 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 100 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    36487.91 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    34797.91 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    35886.95 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 250 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    33152.41 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    31376.60 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    31074.93 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...

debian@varnish:~<span class="nv">$ </span><span class="nb">sudo </span>systemctl restart nginx

debian@varnish:~<span class="nv">$ </span>ab <span class="nt">-t</span> 10 <span class="nt">-c</span> 500 <span class="nt">-k</span> http://127.0.0.1/wordpress/index.php
...
Requests per second:    32747.87 <span class="o">[</span><span class="c">#/sec] (mean)
</span>
...</code></pre></figure>

<p>Efectivamente, el número de peticiones respondidas por segundo ha aumentado considerablemente ya que ahora no hay que realizar todo el proceso que estamos acostumbrados (la petición llega al servidor web, posteriormente al servidor de aplicaciones y el HTML es generado…) sino que ahora, únicamente la primera petición es la que llega al servidor web y por consiguiente, al servidor de aplicaciones, ya que el contenido devuelto es cacheado por el proxy inverso varnish.</p>

<p>Podemos comprobar en los logs de apache que únicamente se ha recibido una petición:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@varnish:~<span class="nv">$ </span><span class="nb">sudo cat</span> /var/log/nginx/access.log

127.0.0.1 - - <span class="o">[</span>10/Feb/2021:17:42:05 +0000] <span class="s2">"GET /wordpress/index.php HTTP/1.1"</span> 301 5 <span class="s2">"-"</span> <span class="s2">"ApacheBench/2.3"</span></code></pre></figure>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="servicios" /><summary type="html"><![CDATA[Instalamos ansible: alvaro@debian:~$ sudo apt update &amp;&amp; sudo apt install ansible Clonamos el repositorio con la receta en su interior: alvaro@debian:~/GitHub$ git clone https://github.com/josedom24/ansible_nginx_fpm_php.git Clonando en 'ansible_nginx_fpm_php'... remote: Enumerating objects: 40, done. remote: Counting objects: 100% (40/40), done. remote: Compressing objects: 100% (27/27), done. remote: Total 40 (delta 0), reused 36 (delta 0), pack-reused 0 Desempaquetando objetos: 100% (40/40), listo. Listamos el contenido del repositorio clonado: alvaro@debian:~/GitHub/ansible_nginx_fpm_php$ ls -l total 44 -rw-r--r-- 1 alvaro alvaro 76 feb 10 18:00 ansible.cfg drwxr-xr-x 2 alvaro alvaro 4096 feb 10 18:00 group_vars -rw-r--r-- 1 alvaro alvaro 98 feb 10 18:00 hosts -rw-r--r-- 1 alvaro alvaro 18092 feb 10 18:00 LICENSE -rw-r--r-- 1 alvaro alvaro 84 feb 10 18:00 README.md drwxr-xr-x 6 alvaro alvaro 4096 feb 10 18:00 roles -rw-r--r-- 1 alvaro alvaro 264 feb 10 18:00 site.yaml Editamos el fichero hosts y establecemos la dirección IP de la máquina que vamos a utilizar para las pruebas: alvaro@debian:~/GitHub/ansible_nginx_fpm_php$ nano hosts [servidores_web] nodo1 ansible_ssh_host=192.168.1.136 ansible_python_interpreter=/usr/bin/python3 Ejecutamos el playbook de ansible para así realizar las modificaciones iniciales en la máquina que utilizaremos para las pruebas: alvaro@debian:~/GitHub/ansible_nginx_fpm_php$ ansible-playbook site.yaml PLAY [servidores_web] ********************************************************************************** TASK [Gathering Facts] ********************************************************************************* ok: [nodo1] TASK [nginx : install nginx, php-fpm] ****************************************************************** changed: [nodo1] TASK [nginx : Copy info.php] *************************************************************************** changed: [nodo1] TASK [nginx : Copy virtualhost default] **************************************************************** changed: [nodo1] RUNNING HANDLER [nginx : restart nginx] **************************************************************** changed: [nodo1] PLAY [servidores_web] ********************************************************************************** TASK [Gathering Facts] ********************************************************************************* ok: [nodo1] TASK [mariadb : ensure mariadb is installed] *********************************************************** changed: [nodo1] TASK [mariadb : ensure mariadb binds to internal interface] ******************************************** changed: [nodo1] RUNNING HANDLER [mariadb : restart mariadb] ************************************************************ changed: [nodo1] PLAY [servidores_web] ********************************************************************************** TASK [Gathering Facts] ********************************************************************************* ok: [nodo1] TASK [wordpress : install unzip] *********************************************************************** changed: [nodo1] TASK [wordpress : download wordpress] ****************************************************************** changed: [nodo1] TASK [wordpress : unzip wordpress] ********************************************************************* changed: [nodo1] TASK [wordpress : create database wordpress] *********************************************************** changed: [nodo1] TASK [wordpress : create user mysql wordpress] ********************************************************* changed: [nodo1] =&gt; (item=localhost) TASK [wordpress : copy wp-config.php] ****************************************************************** changed: [nodo1] RUNNING HANDLER [wordpress : restart nginx] ************************************************************ changed: [nodo1] PLAY RECAP ********************************************************************************************* nodo1 : ok=17 changed=14 unreachable=0 failed=0 Accedemos desde el navegador web a http://IP/wordpress y procedemos con la instalación del CMS, quedando finalmente así: Nos conectamos a la máquina de pruebas e instalamos apache2-utils, que contiene el comando ab que utilizaremos para los benchmarks: debian@varnish:~$ sudo apt install apache2-utils Realizamos diferentes pruebas de rendimiento, alternando entre diversos niveles de concurrencia, para conocer el número de peticiones que es capaz de responder. Entre cada prueba, reiniciamos el servicio para que los resultados no se vean afectados: debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 164.29 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 164.80 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 163.25 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 153.69 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 154.68 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 157.09 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 11170.51 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 12488.91 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 11782.17 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 14262.15 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 14513.48 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 14268.24 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx Una vez conocido los resultados, vamos a hacer uso de un proxy inverso caché conocido como varnish, que cacheará las respuestas del servidor web para así ofrecerlas de una forma mucho más rápida. Para que los clientes no noten diferencia, dicho proxy inverso se encontrará escuchando peticiones en el puerto 80, en lugar de nginx, de manera que modificaremos el VirtualHost para así cambiar el puerto de nginx a por ejemplo, el 8080: root@varnish:~# nano /etc/nginx/sites-available/default listen 8080 default_server; listen [::]:8080 default_server; root@varnish:~# systemctl restart nginx Nos aseguramos que ahora está escuchando peticiones en el 8080: root@varnish:~# netstat -tln Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp6 0 0 :::8080 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN Instalamos varnish: root@varnish:~# apt install varnish Realizamos las correspondientes modificaciones en los ficheros de configuración para que el servicio escuche en el puerto 80 y redirija las peticiones al puerto 8080 (nginx), en caso de ser necesario: root@varnish:~# nano /etc/varnish/default.vcl backend default { .host = "127.0.0.1"; .port = "8080"; } root@varnish:~# nano /etc/default/varnish DAEMON_OPTS="-a :80 \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s malloc,256m" root@varnish:~# nano /lib/systemd/system/varnish.service ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a :80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,256m root@varnish:~# systemctl daemon-reload root@varnish:~# systemctl restart varnish Nos aseguramos que ahora está escuchando peticiones en el 80: root@varnish:~# netstat -tln Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:6082 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::8080 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 ::1:6082 :::* LISTEN Repetimos las pruebas anteriores para así verificar que el número de peticiones respondidas por segundo es ahora mucho mayor, al tener un proxy inverso caché por delante: debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 37704.80 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 38217.53 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 50 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 37134.25 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 37765.69 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 36442.75 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 100 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 36487.91 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 34797.91 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 35886.95 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 250 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 33152.41 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 31376.60 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 31074.93 [#/sec] (mean) ... debian@varnish:~$ sudo systemctl restart nginx debian@varnish:~$ ab -t 10 -c 500 -k http://127.0.0.1/wordpress/index.php ... Requests per second: 32747.87 [#/sec] (mean) ... Efectivamente, el número de peticiones respondidas por segundo ha aumentado considerablemente ya que ahora no hay que realizar todo el proceso que estamos acostumbrados (la petición llega al servidor web, posteriormente al servidor de aplicaciones y el HTML es generado…) sino que ahora, únicamente la primera petición es la que llega al servidor web y por consiguiente, al servidor de aplicaciones, ya que el contenido devuelto es cacheado por el proxy inverso varnish. Podemos comprobar en los logs de apache que únicamente se ha recibido una petición: debian@varnish:~$ sudo cat /var/log/nginx/access.log 127.0.0.1 - - [10/Feb/2021:17:42:05 +0000] "GET /wordpress/index.php HTTP/1.1" 301 5 "-" "ApacheBench/2.3"]]></summary></entry><entry><title type="html">Instalación de un servidor de correo</title><link href="https://www.alvarovf.com/servicios/vps/2021/02/17/instalacion-servidor-correo.html" rel="alternate" type="text/html" title="Instalación de un servidor de correo" /><published>2021-02-17T14:11:00+00:00</published><updated>2021-02-17T14:11:00+00:00</updated><id>https://www.alvarovf.com/servicios/vps/2021/02/17/instalacion-servidor-correo</id><content type="html" xml:base="https://www.alvarovf.com/servicios/vps/2021/02/17/instalacion-servidor-correo.html"><![CDATA[<p>Este es el cuarto artículo referente a la configuración del VPS, una máquina virtual contratada en OVH que cuenta con un direccionamiento público <strong>51.210.109.246</strong>. Además, se ha contratado una zona DNS en la que nombraremos dicha máquina junto a los servicios que despleguemos, en el dominio <strong>iesgn19.es</strong>.</p>

<p>La intención es la de ir desarrollando a lo largo del curso una serie de <em>posts</em> en los que se detallen las configuraciones llevadas a cabo en dicho VPS, así como el mantenimiento de las mismas. En el día de hoy, vamos a instalar un servidor de correo <strong>Postfix</strong>, así como llevar a cabo determinadas configuraciones para ampliar las funcionalidades del mismo.</p>

<p>Antes de comenzar con la instalación de <em>Postfix</em>, es recomendable conocer de una forma más o menos superficial el método de funcionamiento del correo electrónico. Como todos bien sabemos, para enviar un correo electrónico necesitamos conocer la dirección de correo de un usuario, que tendrá la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">usuario@nombre_servicio</code></pre></figure>

<p>De forma general, el nombre del servicio de correo es el nombre del dominio de la organización a la que pertenece el usuario. Por ejemplo, <strong>alvarovf.com</strong>.</p>

<p>Cada vez que enviamos un correo a esa dirección, tenemos que determinar de alguna forma la dirección IP del servidor de correo existente en la organización, siguiendo para ello los siguientes pasos:</p>

<ul>
  <li>
    <p>Si el nombre detrás de la <strong>@</strong> está asociado a un registro <em>A</em> en un servidor DNS, ya conocemos la dirección del servidor de correo.</p>
  </li>
  <li>
    <p>En caso contrario, se consultaría en el servidor DNS por el registro <em>MX</em> que indique el nombre del servidor de correo asociado al nombre de dominio.</p>
  </li>
</ul>

<p>Sin entrar en demasiado detalle, existen determinados conceptos que debemos conocer respecto a los servidores de correo y la infraestructura existente a su alrededor:</p>

<ul>
  <li>
    <p><strong>MUA (<em>Mail User Agent</em>)</strong>: Programa que permite a un usuario, como mínimo, leer y escribir mensajes de correo electrónico. Generalmente conocido como cliente de correo. Por ejemplo, <strong>Evolution</strong>, <strong>Outlook</strong>, <strong>Thunderbird</strong>…</p>
  </li>
  <li>
    <p><strong>MTA (<em>Mail Transfer Agent</em>)</strong>: Programa que transfiere los mensajes de correo electrónico entre máquinas que usan el protocolo <em>SMTP</em>. Un mensaje puede pasar por varios <em>MTA</em> hasta llegar al destino final. Generalmente conocido como servidor de correo. Por ejemplo, <strong>Postfix</strong>, <strong>qmail</strong>, <strong>exim</strong>…</p>
  </li>
  <li>
    <p><strong>MDA (<em>Mail Delivery Agent</em>)</strong>: Programas utilizados por los agentes <em>MTA</em> para entregar el correo electrónico al buzón de un usuario concreto. Esta entrega se puede hacer localmente en el servidor o de forma remota utilizando el protocolo <em>POP3</em> o <em>IMAP</em>.</p>
  </li>
  <li>
    <p><strong>SMTP (<em>Simple Mail Transfer Protocol</em>)</strong>: Protocolo de red utilizado para el intercambio de mensajes de correo electrónico. Existe una mejora del protocolo llamada <em>ESMTP</em> (<em>Enhanced Simple Mail Transfer Protocol</em>).</p>
  </li>
  <li>
    <p><strong>POP3 (<em>Post Office Protocol</em>)</strong>: Protocolo para recuperar correos electrónicos de un <em>MDA</em>. Su principal característica es que se descargan todos los correos.</p>
  </li>
  <li>
    <p><strong>IMAP (<em>Internet Message Access Protocol</em>)</strong>: Protocolo para recuperar correos electrónicos de un <em>MDA</em>. A diferencia del anterior, se sincroniza el estado de los correos entre el servidor y el cliente.</p>
  </li>
</ul>

<p>Por último, de una manera un tanto superficial, vamos a comprender el proceso que se sigue cada vez que un correo electrónico es enviado de un cliente a otro:</p>

<p><img src="https://i.ibb.co/TmYFbL0/mta.png" alt="esquema1" title="Esquema correo" /></p>

<ul>
  <li>
    <p><strong>1.</strong> Un usuario utiliza un <em>MUA</em> para enviar el correo electrónico a su servidor de correos (<em>MTA</em>). Este envío se hace usando el protocolo SMTP. El nombre del servidor tendrá que estar definido en un servidor DNS, y en un principio usaremos el puerto <strong>25/TCP</strong>, estableciendo una conexión no cifrada ni autentificada. Por ese mismo motivo, es más común utilizar el protocolo <em>ESMTP</em>, que utiliza el puerto <strong>587/TCP</strong> y permite la autentificación y el cifrado de la comunicación.</p>
  </li>
  <li>
    <p><strong>2.</strong> El <em>MTA</em> recibe el correo desde el <em>MUA</em>:</p>

    <ul>
      <li>
        <p>Si la dirección del destinatario del correo es la misma que la que controla el servidor receptor, el correo no se envía a ningún <em>MTA</em> y se le da al <em>MDA</em> para que lo guarde en el buzón del usuario destinatario.</p>
      </li>
      <li>
        <p>Si la dirección del destinatario del correo es distinta que la que controla el servidor receptor, el correo se envía al <em>MTA</em> correspondiente a la dirección del destinatario, pudiéndose hacer de dos formas:</p>

        <ul>
          <li>
            <p>Si por cualquier razón el <em>MTA</em> no puede enviar el correo directamente al <em>MTA</em> destino, se tendrá configurado un servidor <em>MTA</em> intermediario (<em>relay</em>) que será el responsable de enviarlo al destinatario final.</p>
          </li>
          <li>
            <p>Si el <em>MTA</em> no tiene configurado un servidor <em>relay</em> intermediario, tendrá que averiguar la dirección IP del servidor correspondiente al nombre de correo del destinatario, normalmente haciendo una consulta <em>MX</em> al servidor DNS. En caso de que existan varios servidores de correo definidos, le intentará mandar el correo al más prioritario (el que tiene el número más pequeño).</p>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>3.</strong> Cuando el correo llega al <em>MTA</em> destino, se pasa el correo al <em>MDA</em> que lo guardará en el buzón del usuario destinatario.</p>
  </li>
  <li>
    <p><strong>4.</strong> El usuario destinatario utilizará una <em>MUA</em> para conectarse al servidor <em>MDA</em> y recuperar el correo:</p>

    <ul>
      <li>
        <p>Puede utilizar el protocolo <em>POP3</em>, por lo que se conectará al servidor <em>POP3</em>. Esta conexión está autentificada y puede estar cifrada. El protocolo <em>POP3</em> suele hacer uso del puerto <strong>110/TCP</strong>. Con este protocolo lo que hacemos es descargar todos los correos desde nuestro buzón remoto a nuestro <em>MUA</em>.</p>
      </li>
      <li>
        <p>Puede usar el protocolo <em>IMAP</em>, por lo que se conectará al servidor <em>IMAP</em>. Esta conexión está autentificada y puede estar cifrada. El protocolo <em>IMAP</em> suele hacer uso del puerto <strong>143/TCP</strong>. Con este protocolo lo que hacemos es sincronizar el estado de los correos desde nuestro buzón remoto a nuestro <em>MUA</em>, de tal manera que podemos acceder desde distintos <em>MUA</em> y obtendremos el mismo estado de los correos en todos ellos. Es imprescindible si vamos a usar un <em>MUA</em> que sea una aplicación web.</p>
      </li>
    </ul>
  </li>
</ul>

<p>Una vez conocido el método de funcionamiento del correo electrónico y algunos conceptos necesarios, todo estará listo para instalar un <strong>MTA</strong> de nombre <strong>Postfix</strong>, que nos permitirá transferir mensajes de correo electrónico entre máquinas que utilicen el protocolo <em>SMTP</em>.</p>

<p>Sin embargo, antes de ello es necesario realizar algunas modificaciones en nuestra zona DNS, pues para poder enviar y recibir correos desde nuestro VPS necesitamos cumplir los siguientes requisitos:</p>

<ul>
  <li>
    <p>En este caso, no es necesario, ya que el VPS es capaz de enviar el correo electrónico al exterior por sí mismo, pero en otras situaciones, sería necesario configurar un servidor de correo intermediario (<em>relay</em>).</p>
  </li>
  <li>
    <p>Necesitamos configurar en nuestra zona DNS un registro <em>MX</em> apuntando a nuestra máquina, necesario para la recepción de correos, pues así el resto de máquinas pueden conocer la IP de nuestro servidor de correo.</p>
  </li>
  <li>
    <p>Necesitamos implementar varias técnicas (al menos un registro <em>SPF</em> del que posteriormente hablaremos) para asegurar que el servidor de correo al que mandamos nuestro correo confíe en nosotros, para así evitar suplantaciones.</p>
  </li>
  <li>
    <p>Necesitamos que nuestra IP pública esté “limpia”. Generalmente, las direcciones IP dinámicas suelen estar incluidas en una lista negra que comprobarán los servicios como Gmail, Hotmail o Yahoo para evitar el <em>spam</em>, ya que previamente han sido utilizadas con dicha finalidad. Podemos comprobar si nuestra dirección IP se encuentra en una lista negra haciendo uso de <a href="https://www.dnsbl.info/dnsbl-database-check.php">esta</a> utilidad.</p>
  </li>
</ul>

<p>El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS <strong>iesgn19.es</strong>, concretamente nombrando un nuevo servicio <strong>mail</strong> mediante un registro <em>A</em> que apunta a la dirección <strong>51.210.109.246</strong>, que será el nombre del servicio al que posteriormente apuntará el registro <em>MX</em>, tal y como podemos apreciar a continuación:</p>

<p><img src="https://i.ibb.co/Pc19xYb/Captura-de-pantalla-de-2021-01-21-09-48-38.png" alt="dns1" title="Zona DNS" /></p>

<p>Muy posiblemente estéis pensando en el motivo por el que he creado un registro <em>A</em> apuntando a la dirección IP de la VPS en lugar de crear un registro <em>CNAME</em> que apunte a un registro <em>A</em> ya existente.</p>

<p>En realidad, podríamos haber creado el registro <em>CNAME</em> que acabamos de mencionar apuntando a un registro <em>A</em> ya existente y funcionaría a la perfección, sin embargo, no es considerado una buena práctica, pues idealmente, los registros que apunten a servidores de correo deben ser registros <em>A</em>.</p>

<p>Desconozco el motivo real, pues lo único que se me ocurre es que la finalidad sea acelerar la petición, ya que se haría una única petición (registro <em>A</em>) en lugar de dos (registro <em>CNAME</em>).</p>

<p>Tras ello, todo estará listo para crear el nuevo registro <em>MX</em> que indicará el servidor al que los servidores de correo externos deben enviar los correos cuyo dominio destinatario sea <strong>iesgn19.es</strong>. Le asignaremos la prioridad máxima (<strong>1</strong>), aunque en este caso es irrelevante, pues únicamente tenemos un servidor, quedando de la siguiente forma:</p>

<p><img src="https://i.ibb.co/hKxYxvP/Captura-de-pantalla-de-2021-01-19-14-06-16.png" alt="dns2" title="Zona DNS" /></p>

<p>Por último, tendremos que implementar al menos una técnica para asegurar que el servidor de correo al que mandamos nuestro correo confíe en nosotros, para así evitar suplantaciones. En este caso, vamos a configurar un registro <em>SPF</em>, no sin antes explicar en qué consiste.</p>

<p>En principio, cualquier máquina puede enviar mensajes de correo a cualquier destino utilizando cualquier remitente, pero esta característica del correo ha sido masivamente utilizada para inundar de mensajes no deseados a los servidores de correo y hacer suplantaciones del remitente (<em>email spoofing</em>), por lo que se ha generalizado la implementación de medidas complementarias para reducir al máximo este problema y hoy en día, la mayoría de servidores de correo o rechazan o mandan a la bandeja de <em>spam</em> los mensajes que lleguen desde servidores que no implementen algún mecanismo adicional de autenticación.</p>

<p>Como ya hemos mencionado, en este caso vamos a utilizar <strong><em>SPF</em></strong> (<em>Sender Policy Framework</em>), un mecanismo de autenticación que describe de forma explícita mediante un registro DNS de tipo <em>TXT</em> las direcciones IP y nombres DNS autorizados a enviar correo desde un determinado dominio.</p>

<p>Se pueden especificar diversos campos en dicho registro, pero en este caso, que tenemos un solo equipo con una dirección IPv4, pondremos un registro SPF parecido al siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">IN  TXT <span class="s2">"v=spf1 mx ~all"</span></code></pre></figure>

<p>Dentro del mismo, podemos hacer referencia a nuestro servidor de correo de diferentes formas:</p>

<ul>
  <li>
    <p><strong>a</strong>: La IP de un registro A de un nombre del DNS.</p>
  </li>
  <li>
    <p><strong>mx</strong>: Registro MX del DNS del dominio.</p>
  </li>
  <li>
    <p><strong>ptr</strong>: Registro PTR del servidor de correo.</p>
  </li>
  <li>
    <p><strong>ip4</strong>: Direcciones IPv4.</p>
  </li>
  <li>
    <p><strong>ip6</strong>: Direcciones IPv6.</p>
  </li>
</ul>

<p>En este caso, como la dirección IP a la que apunta el registro <em>MX</em> coincide con la máquina que va a enviar el correo al exterior, he referenciado al servidor de correo mediante <strong>mx</strong>, aunque también podría haber puesto <strong>ip4:51.210.109.246/32</strong>.</p>

<p>Es importante mencionar la importancia del signo que aparece antes de <strong>all</strong>, ya que podemos indicarle a los servidores destinatarios lo que deben hacer si reciben correo desde otra máquina diferente a las referenciadas en el registro anterior:</p>

<ul>
  <li>
    <p><strong>-</strong>: Descartar el mensaje.</p>
  </li>
  <li>
    <p><strong>~</strong>: Clasificarlo como <em>spam</em>.</p>
  </li>
  <li>
    <p><strong>?</strong>: Aceptar el mensaje.</p>
  </li>
</ul>

<p>En este caso, no vamos a <em>pillarnos los dedos</em> y vamos a indicar que los correos electrónicos recibidos desde una máquina distinta a la VPS se clasifique como <em>spam</em>, pero evitando que se descarte, para así comprobar que el envío se está realizando correctamente.</p>

<p>De esta forma, el correo que enviemos desde la máquina VPS pasará los filtros SPF en el destino y la mayoría de nuestros correos llegarán al destino con poca probabilidad de que se clasifiquen como <em>spam</em>. El registro tendrá finalmente la siguiente forma:</p>

<p><img src="https://i.ibb.co/XVg4sdq/Captura-de-pantalla-de-2021-01-19-14-10-03.png" alt="dns3" title="Zona DNS" /></p>

<p>Una vez finalizada la configuración en la zona DNS de nuestro dominio, todo estará listo para proceder con la instalación y configuración inicial del servidor de correos <strong>Postfix</strong>.</p>

<h2 id="instalación-de-postfix">Instalación de Postfix</h2>

<p>Además de llevar a cabo la instalación del paquete <strong>postfix</strong>, instalaremos a su vez el paquete <strong>bsd-mailx</strong>, que nos proporcionará las utilidades necesarias para llevar a cabo el envío de correo (<em>mail</em>), no sin antes actualizar la lista de la paquetería disponible, así como asegurar que toda la paquetería instalada en la máquina se encuentra en su última versión, por lo que ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt update <span class="o">&amp;&amp;</span> apt upgrade <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>postfix bsd-mailx</code></pre></figure>

<p>Durante la instalación, nos aparecerá una ventana en la que debemos seleccionar una opción para que el paquete se configure en base a nuestras necesidades. En nuestro caso, dado que la máquina es capaz de enviar y recibir correo electrónico por sí misma a través del protocolo <em>SMTP</em>, seleccionaremos la opción <strong>Internet Site</strong>, de la siguiente manera:</p>

<p><img src="https://i.ibb.co/9n1mZ7H/Captura-de-pantalla-de-2021-01-19-13-45-38.png" alt="postfix1" title="Postfix" /></p>

<p>En el siguiente paso de la instalación se nos pedirá el nombre del sistema de correo, es decir, el nombre que se utilizará para cualificar los correos electrónicos en cuestión, o en otras palabras, lo que irá indicado después de <strong>@</strong>. Por ejemplo, para el correo <strong>debian@iesgn19.es</strong>, el valor correcto sería <strong>iesgn19.es</strong>, quedando de la siguiente forma:</p>

<p><img src="https://i.ibb.co/S5Ffk9m/CapturaX.jpg" alt="postfix2" title="Postfix" /></p>

<p>El paquete <strong>postfix</strong> ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto un <em>socket TCP/IP</em> en el puerto por defecto del protocolo <em>SMTP</em> (<strong>25</strong>) que estará escuchando peticiones en todas las interfaces de la máquina (<strong>0.0.0.0</strong>), así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tln</span>
Active Internet connections <span class="o">(</span>only servers<span class="o">)</span>
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.1:3306          0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:80              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:25              0.0.0.0:<span class="k">*</span>               LISTEN     
tcp        0      0 0.0.0.0:443             0.0.0.0:<span class="k">*</span>               LISTEN     
tcp6       0      0 :::80                   :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::25585                :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::22                   :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::25                   :::<span class="k">*</span>                    LISTEN     
tcp6       0      0 :::443                  :::<span class="k">*</span>                    LISTEN</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-t</strong>: Filtramos únicamente para las conexiones que utilizan el protocolo TCP.</li>
  <li><strong>-l</strong>: Filtramos únicamente para los <em>sockets</em> que están actualmente escuchando peticiones (<strong>State = LISTEN</strong>).</li>
  <li><strong>-n</strong>: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos.</li>
</ul>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería. A pesar de estar escuchando peticiones en todas las interfaces de la máquina, eso no supone que cualquier persona ajena a nosotros pueda utilizar nuestro servidor para enviar correos, ya que para ello existe indicada una directiva <strong>mynetworks</strong> en el fichero <strong>/etc/postfix/main.cf</strong> que se encuentra limitada al direccionamiento <strong>127.0.0.0/8</strong>, lo que supone que nadie podrá hacer <em>relay</em> en nuestro servidor.</p>

<p>Sin embargo, dado que el protocolo <em>SMTP</em> también utiliza el puerto <strong>25/TCP</strong> para la recepción de correos (además de para hacer <em>relay</em>), será necesario tenerlo abierto para todo el mundo, pues de lo contrario, imposibilitaríamos la recepción de los mismos.</p>

<h2 id="envío-de-correos-desde-el-servidor">Envío de correos desde el servidor</h2>

<p>Una vez llevada a cabo toda la configuración necesaria, todo estará listo para hacer uso de la utilidad <code class="language-plaintext highlighter-rouge">mail</code> para enviar correos al exterior. En este caso, vamos a enviar un correo de prueba a mi correo personal, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span>mail avacaferreras@gmail.com
Subject: Correo de prueba.
Buenos días, esto es un correo de prueba.
Cc:</code></pre></figure>

<p>En este caso, hemos redactado un correo desde el usuario <strong>debian</strong> al correo destino <strong>avacaferreras@gmail.com</strong>, cuyo asunto será “<strong>Correo de prueba.</strong>” y su contenido, “<strong>Buenos días, esto es un correo de prueba.</strong>”. Considero necesario mencionar que en caso de hacer uso de la utilidad de línea de comandos para enviar correos, finalizaremos la escritura del mismo con la combinación de teclas <strong>CTRL + D</strong>.</p>

<p>En un principio, el envío del correo electrónico se ha producido correctamente, sin embargo, vamos a proceder a verificarlo visualizando para ello los <em>logs</em> producidos por dicho envío, en el fichero <strong>/var/log/mail.log</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span><span class="nb">cat</span> /var/log/mail.log
Jan 21 08:55:12 vps postfix/pickup[15685]: 1CD67E13D1: <span class="nv">uid</span><span class="o">=</span>1000 <span class="nv">from</span><span class="o">=</span>&lt;debian&gt;
Jan 21 08:55:12 vps postfix/cleanup[15693]: 1CD67E13D1: message-id<span class="o">=</span>&lt;20210121075512.1CD67E13D1@vps.iesgn19.es&gt;
Jan 21 08:55:12 vps postfix/qmgr[4212]: 1CD67E13D1: <span class="nv">from</span><span class="o">=</span>&lt;debian@iesgn19.es&gt;, <span class="nv">size</span><span class="o">=</span>445, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Jan 21 08:55:12 vps postfix/smtp[15695]: connect to gmail-smtp-in.l.google.com[2a00:1450:400c:c01::1b]:25: Network is unreachable
Jan 21 08:55:12 vps postfix/smtp[15695]: 1CD67E13D1: <span class="nv">to</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">relay</span><span class="o">=</span>gmail-smtp-in.l.google.com[172.253.120.26]:25, <span class="nv">delay</span><span class="o">=</span>0.59, <span class="nv">delays</span><span class="o">=</span>0.01/0/0.44/0.15, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>250 2.0.0 OK  1611215712 y9si3791494wrs.536 - gsmtp<span class="o">)</span>
Jan 21 08:55:12 vps postfix/qmgr[4212]: 1CD67E13D1: removed</code></pre></figure>

<p>Como se puede apreciar en la penúltima línea mostrada, el estado del correo enviado desde <strong>debian@iesgn19.es</strong> a <strong>avacaferreras@gmail.com</strong> es <strong>sent</strong> (<em>250</em>), por lo que podemos asegurar que el envío se ha producido correctamente desde nuestro extremo.</p>

<p>Todavía queda verificar que la recepción del mismo también se ha llevado a cabo tal y como debería, de manera que accederé a mi correo personal y comprobaré si ha aparecido en la bandeja de entrada:</p>

<p><img src="https://i.ibb.co/L0VSwbZ/Captura-de-pantalla-de-2021-01-21-08-56-09.png" alt="gmail1" title="Gmail" /></p>

<p>Efectivamente, la recepción del correo electrónico ha sido correcta, gracias a haber indicado en nuestra zona DNS un registro <em>SPF</em> para que <em>Gmail</em> pueda verificar la procedencia de dicho correo, y por tanto, pueda confiar en nosotros.</p>

<p>De igual forma, podremos ver el contenido original de dicho correo electrónico junto a sus cabeceras pulsando para ello en los tres puntos ubicados en la parte derecha del mismo, seleccionando tras ello la opción “<strong>Mostrar original</strong>”, que tal y como se puede apreciar, en este caso tiene la siguiente estructura:</p>

<p><img src="https://i.ibb.co/qkKy641/Captura-de-pantalla-de-2021-01-21-08-56-17.png" alt="gmail2" title="Gmail" /></p>

<p>Si nos fijamos con detenimiento, la directiva <strong>Received-SPF</strong> tiene el valor <strong>pass</strong>, lo que significa que la validación del remitente ha sido efectiva gracias al registro <em>SPF</em> que previamente hemos definido, en el que hemos autorizado a la dirección IP <strong>51.210.109.246</strong> a enviar correos electrónicos desde el dominio <strong>iesgn19.es</strong>.</p>

<h2 id="recepción-de-correos-desde-el-servidor">Recepción de correos desde el servidor</h2>

<p>Una vez que hemos comprobado que el envío de correos desde nuestra máquina al exterior funciona como debería, vamos a realizar el procedimiento inverso, en el que vamos a recibir correos en nuestra máquina que hayan sido enviados desde máquinas ajenas.</p>

<p>Para ello, vamos a responder al mensaje en <em>Gmail</em>, que en este caso irá destinado al usuario <strong>debian</strong> existente en el dominio <strong>iesgn19.es</strong>, cuyo contenido será “<strong>Prueba de respuesta.</strong>”, de manera que el resultado final sería el siguiente:</p>

<p><img src="https://i.ibb.co/yVZcHkL/Captura-de-pantalla-de-2021-01-21-08-56-49.png" alt="gmail3" title="Gmail" /></p>

<p>En un principio, el envío y la recepción del correo electrónico se ha producido correctamente, sin embargo, vamos a proceder a verificarlo visualizando para ello los <em>logs</em> producidos por la correspondiente recepción, en el fichero <strong>/var/log/mail.log</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span><span class="nb">cat</span> /var/log/mail.log
Jan 21 08:56:58 vps postfix/smtpd[15713]: connect from mail-wm1-f42.google.com[209.85.128.42]
Jan 21 08:56:58 vps postfix/smtpd[15713]: AA1CDE13D0: <span class="nv">client</span><span class="o">=</span>mail-wm1-f42.google.com[209.85.128.42]
Jan 21 08:56:58 vps postfix/cleanup[15720]: AA1CDE13D0: message-id<span class="o">=</span>&lt;CANR0p-363sCZ-+s7CsHmpnD-Cr-CetvtTFwWZS_MUkZpJFzZUA@mail.gmail.com&gt;
Jan 21 08:56:58 vps postfix/qmgr[4212]: AA1CDE13D0: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>3381, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Jan 21 08:56:58 vps postfix/local[15721]: AA1CDE13D0: <span class="nv">to</span><span class="o">=</span>&lt;debian@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span><span class="nb">local</span>, <span class="nv">delay</span><span class="o">=</span>0.02, <span class="nv">delays</span><span class="o">=</span>0.01/0.01/0/0, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>delivered to mailbox<span class="o">)</span>
Jan 21 08:56:58 vps postfix/qmgr[4212]: AA1CDE13D0: removed
Jan 21 08:56:58 vps postfix/smtpd[15713]: disconnect from mail-wm1-f42.google.com[209.85.128.42] <span class="nv">ehlo</span><span class="o">=</span>2 <span class="nv">starttls</span><span class="o">=</span>1 <span class="nv">mail</span><span class="o">=</span>1 <span class="nv">rcpt</span><span class="o">=</span>1 <span class="nv">bdat</span><span class="o">=</span>1 <span class="nv">quit</span><span class="o">=</span>1 <span class="nv">commands</span><span class="o">=</span>7</code></pre></figure>

<p>Como se puede apreciar en la antepenúltima línea mostrada, el estado del correo enviado desde <strong>avacaferreras@gmail.com</strong> a <strong>debian@iesgn19.es</strong> es <strong>sent</strong> (<em>250</em>), por lo que podemos asegurar que la recepción se ha producido correctamente desde nuestro extremo.</p>

<p>Tras ello, volveré a hacer uso de la utilidad <code class="language-plaintext highlighter-rouge">mail</code> para acceder a la bandeja de entrada (buzón) del usuario <strong>debian</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span>mail
Mail version 8.1.2 01/15/2001.  Type ? <span class="k">for </span>help.
<span class="s2">"/var/mail/debian"</span>: 1 message 1 new
<span class="o">&gt;</span>N  1 avacaferreras@gma  Thu Jan 21 08:56   70/3476  Re: Correo de prueba.</code></pre></figure>

<p>Efectivamente, la recepción del correo electrónico ha sido correcta, gracias a haber indicado en nuestra zona DNS un registro <em>MX</em> para que <em>Gmail</em> pueda encontrar la dirección IP de la máquina a la que debe enviar los correos para el dominio <strong>iesgn19.es</strong>.</p>

<p>De igual forma, podremos ver el contenido de dicho correo electrónico junto a sus cabeceras eligiendo numéricamente el correo que queremos visualizar (en este caso, <strong>1</strong>), pudiendo apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Message 1:
From avacaferreras@gmail.com  Thu Jan 21 08:56:58 2021
X-Original-To: debian@iesgn19.es
...
From: <span class="o">=</span>?UTF-8?Q?<span class="o">=</span><span class="nv">C3</span><span class="o">=</span>81lvaro_Vaca_Ferreras?<span class="o">=</span> &lt;avacaferreras@gmail.com&gt;
Date: Thu, 21 Jan 2021 08:56:47 +0100
Subject: Re: Correo de prueba.
To: Debian &lt;debian@iesgn19.es&gt;
Content-Type: multipart/alternative<span class="p">;</span> <span class="nv">boundary</span><span class="o">=</span><span class="s2">"0000000000007f234505b9646a06"</span>

<span class="nt">--0000000000007f234505b9646a06</span>
Content-Type: text/plain<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span><span class="s2">"UTF-8"</span>
Content-Transfer-Encoding: quoted-printable

Prueba de respuesta.
...</code></pre></figure>

<p>Como se puede apreciar, el contenido al completo de dicho correo electrónico ha sido correctamente visualizado desde nuestra utilidad de línea de comandos, por lo que podemos verificar que la recepción de correos está totalmente operativa.</p>

<h2 id="alias-y-redirecciones">Alias y redirecciones</h2>

<p>Una característica muy interesante es que podemos permitir a los procesos que se encuentren en ejecución en la máquina enviar correos para así informar sobre su estado, por ejemplo, podemos crear una tarea <code class="language-plaintext highlighter-rouge">cron</code> en la que el resultado de la ejecución de dicho comando se notifique mediante un correo a un usuario concreto.</p>

<p>En este caso, vamos a crear una nueva tarea que nos envíe cada minuto la hora del sistema (resultado de la ejecución del comando <code class="language-plaintext highlighter-rouge">date</code>), ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# crontab <span class="nt">-e</span></code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-e</strong>: Permite realizar modificaciones en el <em>crontab</em> actual y cargar de forma automática las modificaciones llevadas a cabo.</li>
</ul>

<p>En este caso, tendremos que indicar como mínimo dos nuevas directivas, siendo la primera de ellas aquella en la que indicaremos el usuario al que queremos enviar dichos correos informando del resultado de las ejecuciones (generalmente suele ser <strong>root</strong>) y la segunda, el comando en cuestión, precedido de la frecuencia de ejecución.</p>

<p>Explicar cómo se define la frecuencia de ejecución de cualquier comando indicado en el <em>crontab</em> se saldría del objetivo de este artículo, de manera que <a href="https://linux.die.net/man/5/crontab">aquí</a> se podrá encontrar una página del manual en la que se explica de forma detallada y con ejemplos que ayudan a su comprensión. El resultado final sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">MAILTO <span class="o">=</span> root
<span class="k">*</span> <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> <span class="nb">date</span></code></pre></figure>

<p>El hecho de haber indicado 5 <strong>*</strong> supone la ejecución de dicho comando 1 vez por minuto, por lo que a lo largo de una hora recibiremos 60 correos informando sobre la ejecución de dicho comando.</p>

<p>Es importante mencionar que la directiva <strong>MAILTO</strong> afecta a todos los comandos indicados tras la misma, de manera que el resultado de aquellos que se hayan indicado previamente no será notificado por correo.</p>

<p>Tras esperar un minuto para así dar lugar a la ejecución de dicho comando, volveré a hacer uso de la utilidad <code class="language-plaintext highlighter-rouge">mail</code> para acceder a la bandeja de entrada (buzón) del usuario <strong>root</strong>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# mail
Mail version 8.1.2 01/15/2001.  Type ? <span class="k">for </span>help.
<span class="s2">"/var/mail/root"</span>: 1 message 1 new
<span class="o">&gt;</span>N  1 root@iesgn19.es    Thu Jan 21 09:32   22/683   Cron &lt;root@vps&gt; <span class="nb">date</span></code></pre></figure>

<p>Efectivamente, la recepción local del correo electrónico ha sido correcta, así que procederemos a visualizar el contenido de dicho correo electrónico junto a sus cabeceras eligiendo numéricamente al igual que en el caso anterior, pudiendo apreciar lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Message 1:
From root@iesgn19.es  Thu Jan 21 09:32:01 2021
X-Original-To: root
From: root@iesgn19.es <span class="o">(</span>Cron Daemon<span class="o">)</span>
To: root@iesgn19.es
Subject: Cron &lt;root@vps&gt; <span class="nb">date
</span>MIME-Version: 1.0
Content-Type: text/plain<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>UTF-8
Content-Transfer-Encoding: 8bit
X-Cron-Env: &lt;<span class="nv">MAILTO</span><span class="o">=</span>root&gt;
X-Cron-Env: &lt;<span class="nv">SHELL</span><span class="o">=</span>/bin/sh&gt;
X-Cron-Env: &lt;<span class="nv">HOME</span><span class="o">=</span>/root&gt;
X-Cron-Env: &lt;<span class="nv">PATH</span><span class="o">=</span>/usr/bin:/bin&gt;
X-Cron-Env: &lt;<span class="nv">LOGNAME</span><span class="o">=</span>root&gt;
Date: Thu, 21 Jan 2021 09:32:01 +0100 <span class="o">(</span>CET<span class="o">)</span>

Thu 21 Jan 2021 09:32:01 AM CET</code></pre></figure>

<p>Como se puede apreciar, el contenido al completo de dicho correo electrónico ha sido correctamente visualizado desde nuestra utilidad de línea de comandos, por lo que podemos verificar que la recepción de correos para aquellas tareas <code class="language-plaintext highlighter-rouge">cron</code> configuradas se encuentra totalmente operativa.</p>

<p>En este caso estamos llevando a cabo un ejemplo que carece de sentido, pues mi intención es únicamente mostrar el funcionamiento, pero en situaciones reales, puede llegar a ser bastante útil, por ejemplo, para avisar al administrador del estado de la copia de seguridad diaria.</p>

<p>Sin embargo, todavía podemos ir un paso más allá y hacer uso de los <strong>alias</strong>, que nos permitirán redirigir el correo que un determinado usuario reciba al buzón de otro usuario ubicado en la misma máquina. Por ejemplo, podríamos redirigir el correo de todos los usuarios de la máquina al buzón de <strong>root</strong> y posteriormente, redirigir una vez más los correos entrantes para <strong>root</strong> a los usuarios administradores del sistema, para así gestionarlo de forma centralizada.</p>

<p>Para esta ocasión, he decidido redirigir todos los correos entrantes al usuario <strong>root</strong> al buzón del usuario <strong>debian</strong>, consiguiendo por tanto que el correo con el resultado de la ejecución de la tarea <code class="language-plaintext highlighter-rouge">cron</code> acabe finalmente en el buzón de <strong>debian</strong>. Para ello, tendremos que modificar el fichero <strong>/etc/aliases</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/aliases</code></pre></figure>

<p>El contenido por defecto de dicho fichero es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postmaster:    root</code></pre></figure>

<p>Dentro del mismo, tendremos que añadir una línea por cada <strong>alias</strong> que deseemos crear, en la que indicaremos al principio el usuario del que queremos redirigir los correos y posteriormente, el usuario que actuará como destinatario final. En este caso, el resultado final sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postmaster:    root
root:    debian</code></pre></figure>

<p>Dado que hemos modificado un fichero de configuración, tendremos que ejecutar el comando <code class="language-plaintext highlighter-rouge">newaliases</code> para que los cambios surtan efecto, de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# newaliases</code></pre></figure>

<p>El nuevo <strong>alias</strong> ya habrá sido generado y se encuentra actualmente en vigor, de manera que el resultado de la próxima ejecución de la tarea <code class="language-plaintext highlighter-rouge">cron</code> acabará finalmente en el buzón de <strong>debian</strong>, tal y como se puede apreciar:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span>mail
Mail version 8.1.2 01/15/2001.  Type ? <span class="k">for </span>help.
<span class="s2">"/var/mail/debian"</span>: 1 message 1 new
<span class="o">&gt;</span>N  1 root@iesgn19.es    Thu Jan 21 09:36   22/683   Cron &lt;root@vps&gt; <span class="nb">date</span></code></pre></figure>

<p>Una vez más, la recepción local del correo electrónico ha sido correcta, no siendo necesario visualizar de nuevo el contenido de dicho correo.</p>

<p>Como hemos podido apreciar, los <strong>alias</strong> funcionan perfectamente entre usuarios existentes en la misma máquina, ¿pero qué ocurriría si quisiésemos reenviar dichos a un correo personal como puede ser <em>Gmail</em>? Para ello, tendríamos que acudir a las <strong>redirecciones</strong>, que nos permitirán enviar el correo que llegue a un usuario a una cuenta de correo exterior.</p>

<p>Para esta ocasión, he decidido redirigir todos los correos entrantes al usuario <strong>debian</strong> a mi correo electrónico personal, consiguiendo por tanto que el correo con el resultado de la ejecución de la tarea <code class="language-plaintext highlighter-rouge">cron</code> acabe finalmente en <em>Gmail</em>. Para ello, tendremos que añadir una nueva línea (o tantas como deseemos) al fichero <strong>~/.forward</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">debian@vps:~<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"avacaferreras@gmail.com"</span> <span class="o">&gt;&gt;</span> ~/.forward</code></pre></figure>

<p>La nueva <strong>redirección</strong> ya habrá sido generada y se encuentra actualmente en vigor, de manera que el resultado de la próxima ejecución de la tarea <code class="language-plaintext highlighter-rouge">cron</code> acabará finalmente en mi correo electrónico personal, tal y como se puede apreciar:</p>

<p><img src="https://i.ibb.co/VSLMdJy/forward.jpg" alt="gmail4" title="Gmail" /></p>

<p>Tal y como hemos definido, la <strong>redirección</strong> a mi correo electrónico personal se ha llevado a cabo correctamente y he recibido en el mismo el resultado de la ejecución de la tarea, que como previamente he mencionado, puede llegar a ser algo muy útil en determinadas situaciones.</p>

<p>Por último, antes de finalizar con los <strong>alias</strong> y <strong>redirecciones</strong> es importante revertir todos los cambios realizados, para así retomar el comportamiento normal y deseado.</p>

<h2 id="configuración-de-dkim">Configuración de DKIM</h2>

<p>Previamente hemos introducido el concepto del <em>SPF</em>, un mecanismo de autenticación para que los servidores de correo destinatarios puedan verificar nuestra identidad. Como se puede suponer, existen más mecanismos, como por ejemplo <strong><em>DKIM</em></strong> o <strong><em>DMARC</em></strong>.</p>

<p>En este caso vamos a implementar además <strong><em>DKIM</em></strong> (<em>DomainKeys Identified Mail</em>), un método de autenticación pensado principalmente para corroborar la procedencia del correo y asegurar que el mensaje no ha sido modificado durante la transferencia del mismo, consistente en publicar mediante un registro <em>TXT</em> en el servidor DNS la clave pública del servidor de correos.</p>

<p>Posteriormente, se firmarán con la correspondiente clave privada todos los mensajes emitidos, de manera que el receptor podrá verificar cada correo emitido utilizando la clave pública.</p>

<p>Para configurar DKIM en nuestro servidor necesitaremos instalar los paquetes <strong>opendkim</strong> y <strong>opendkim-tools</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>opendkim opendkim-tools</code></pre></figure>

<p>Una vez instalados los paquetes necesarios, tendremos que llevar a cabo una serie de modificaciones en el fichero de configuración ubicado en <strong>/etc/opendkim.conf</strong>, el cuál editaremos ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/opendkim.conf</code></pre></figure>

<p>El contenido por defecto de dicho fichero es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Syslog                  <span class="nb">yes
</span>UMask                   007
PidFile                 /var/run/opendkim/opendkim.pid
OversignHeaders         From
TrustAnchorFile         /usr/share/dns/root.key
UserID                  opendkim
Socket                  <span class="nb">local</span>:/var/spool/postfix/opendkim/opendkim.sock</code></pre></figure>

<p>Dentro del mismo, tendremos que realizar las siguientes modificaciones:</p>

<ul>
  <li><strong>Socket</strong>: Modificaremos el <em>socket UNIX</em> actualmente configurado por un <em>socket TCP/IP</em> en el puerto <strong>8892</strong> en <em>localhost</em>, para evitar problemas de conexión entre los servicios.</li>
  <li><strong>Domain</strong>: Indicaremos el dominio para el que queremos configurar el mecanismo <em>DKIM</em>. En este caso, <strong>iesgn19.es</strong>.</li>
  <li><strong>KeyFile</strong>: Indicamos la ruta en la que se alojará la clave privada que posteriormente generaremos. El nombre de la misma estará compuesto por <strong>[selector].private</strong>. En este caso, <strong>/etc/opendkim/keys/iesgn19.es/pruebadkim.private</strong>.</li>
  <li><strong>Selector</strong>: Indicamos un nombre único que posteriormente debemos utilizar a la hora de subir la clave pública al servidor DNS, para que el destinatario pueda identificarla fácilmente. En este caso, <strong>pruebadkim</strong>.</li>
</ul>

<p>El resultado final sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Syslog                  <span class="nb">yes
</span>UMask                   007
PidFile                 /var/run/opendkim/opendkim.pid
OversignHeaders         From
TrustAnchorFile         /usr/share/dns/root.key
UserID                  opendkim
Socket                  inet:8892@localhost
Domain                  iesgn19.es
KeyFile                 /etc/opendkim/keys/iesgn19.es/pruebadkim.private
Selector                pruebadkim</code></pre></figure>

<p>Una vez realizadas las modificaciones oportunas, tendremos que modificar también el fichero <strong>/etc/default/opendkim</strong> para indicar de nuevo el <em>socket TCP/IP</em> que se utilizará para comunicar los servicios <strong>Postfix</strong> y <strong>opendkim</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/default/opendkim</code></pre></figure>

<p>Dentro del mismo, tendremos que referenciar al <em>socket TCP/IP</em> ubicado en el puerto <strong>8892</strong> de la máquina local (<em>localhost</em>), quedando de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">SOCKET</span><span class="o">=</span>inet:8892@localhost</code></pre></figure>

<p>Tras ello, guardaremos los cambios y saldremos, para así proceder a modificar finalmente el fichero referente a <strong>Postfix</strong>, para que así utilice dicho mecanismo para firmar los correos salientes. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/main.cf</code></pre></figure>

<p>En su interior tendremos que introducir las líneas necesarias para indicarle entre otras cosas, el <em>socket TCP/IP</em> que ha de utilizar para el firmado de dichos correos, siendo las mismas las siguientes:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">milter_default_action <span class="o">=</span> accept
milter_protocol <span class="o">=</span> 2
smtpd_milters <span class="o">=</span> inet:localhost:8892
non_smtpd_milters <span class="o">=</span> <span class="nv">$smtpd_milters</span></code></pre></figure>

<p>Todos los ficheros de configuración necesarios han sido correctamente modificados, de manera que únicamente nos faltaría generar dicho par de claves, siendo posteriormente utilizada la clave privada para firmar los correos y la clave pública (que tendremos que exponer en un registro <em>TXT</em> del DNS) para comprobar dicha firma.</p>

<p>Como previamente hemos definido, el par de claves deberá encontrarse dentro de <strong>/etc/opendkim/keys/</strong>, en un subdirectorio con el nombre del dominio, en este caso, <strong>iesgn19.es/</strong>, así que procederemos a la creación de dicho subdirectorio haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">mkdir</span> <span class="nt">-p</span> /etc/opendkim/keys/iesgn19.es</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-p</strong>: Indica que se cree el directorio padre en caso de no existir.</li>
</ul>

<p>El nuevo directorio ya habrá sido generado, sin embargo, dado que el contenido que se va a ubicar en el mismo es de carácter sensible, tendremos que protegerlo adecuadamente para así evitar su lectura por personas no deseadas, cambiando para ello los permisos a <strong>700</strong> y el usuario y grupo propietario a <strong>opendkim</strong>, ejecutando para ello los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">chmod </span>700 /etc/opendkim/keys/iesgn19.es/
root@vps:~# <span class="nb">chown </span>opendkim:opendkim /etc/opendkim/keys/iesgn19.es/</code></pre></figure>

<p>Tras ello, y con la finalidad de trabajar de una forma más comoda, nos moveremos dentro de dicho directorio haciendo uso del comando <code class="language-plaintext highlighter-rouge">cd</code>, para así proceder con la creación del par de claves. La creación es bastante sencilla, pues para ello acudiremos a la utilidad <code class="language-plaintext highlighter-rouge">opendkim-genkey</code>, de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/etc/opendkim/keys/iesgn19.es# opendkim-genkey <span class="nt">-s</span> pruebadkim <span class="nt">-d</span> iesgn19.es <span class="nt">-b</span> 1024</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-s</strong>: Indicamos el nombre del <em>selector</em> para el que queremos generar el par de claves. En este caso, <strong>pruebadkim</strong>.</li>
  <li><strong>-d</strong>: Indicamos el dominio que va a hacer uso del par de claves para firmar los correos. En este caso, <strong>iesgn19.es</strong>.</li>
  <li><strong>-b</strong>: Indicamos el tamaño de la clave, que no deberá ser demasiado grande, para evitar las limitaciones de los registros <em>TXT</em> en el DNS. En este caso, <strong>1024</strong> bits.</li>
</ul>

<p>Una vez realizada la ejecución de dicho comando, habrán aparecido dos nuevos ficheros en el directorio actual, que podremos verificar listando el contenido del mismo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/etc/opendkim/keys/iesgn19.es# <span class="nb">ls</span> <span class="nt">-l</span>
total 8
<span class="nt">-rw-------</span> 1 root root 1679 Feb 15 16:36 pruebadkim.private
<span class="nt">-rw-------</span> 1 root root  512 Feb 15 16:36 pruebadkim.txt</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>pruebadkim.private</strong>: Contiene la clave privada y será utilizada para firmar los correos electrónicos salientes.</li>
  <li><strong>pruebadkim.txt</strong>: Contiene la clave pública y será utilizada por los servidores de correo para verificar la firma sobre los correos entrantes.</li>
</ul>

<p>Únicamente nos faltaría añadir el correspondiente registro <em>TXT</em> a nuestra zona DNS que contenga dicha clave pública para poder empezar a hacer uso de este mecanismo, así que antes de nada, tendremos que visualizarla, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/etc/opendkim/keys/iesgn19.es# <span class="nb">cat </span>pruebadkim.txt
pruebadkim._domainkey   IN      TXT     <span class="o">(</span> <span class="s2">"v=DKIM1; h=sha256; k=rsa; "</span>
          <span class="s2">"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhxmxXURJ3QSnnRu4SW9aK3o2Uq8CNwckIzZTdrnA7tWhi1NXrpxPfx0EHOmF1LuJC23eSLbbmy5/xyT6hEnSToE3eNHHd+ZYezmVzi2lZtyoeqxWao15q4WWYxvF99AxLNg3CnXDxuh4T5wtMXBlcysn38iMsTQI+VnGFUxu9xQIDAQAB"</span> <span class="o">)</span>  <span class="p">;</span> <span class="nt">-----</span> DKIM key pruebadkim <span class="k">for </span>iesgn19.es</code></pre></figure>

<p>Es importante mencionar que el nombre del registro <em>TXT</em> debe estar compuesto por <strong>[selector]._domainkey</strong>, que en mi caso sería <strong>pruebadkim._domainkey</strong> y quedaría finalmente de la siguiente forma:</p>

<p><img src="https://i.ibb.co/fQnrxWg/dkim7.jpg" alt="dns4" title="Zona DNS" /></p>

<p>Es recomendable una vez realizada la modificación en la zona DNS llevar a cabo una comprobación utilizando para ello una <a href="https://mxtoolbox.com/dkim.aspx">herramienta</a> externa que compruebe que el registro <em>TXT</em> es correcto, que nos devolverá unos resultados fácilmente interpretables:</p>

<p><img src="https://i.ibb.co/Y45TyLk/dkim6.jpg" alt="dkim1" title="Comprobación DKIM" /></p>

<p>Como se puede apreciar, en mi caso, el registro <em>TXT</em> ha sido correctamente definido, sin embargo, esto no significa que mi máquina sea capaz de enviar firmados los correos salientes, ya que la herramienta previamente mencionada únicamente lleva a cabo comprobaciones sobre la zona DNS.</p>

<p>Para verificar que nuestro nuevo mecanismo de autenticación está funcionando correctamente, tendremos que reiniciar los servicios <strong>Postfix</strong> y <strong>opendkim</strong> para así aplicar los cambios llevados a cabo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/etc/opendkim/keys/iesgn19.es# systemctl restart opendkim postfix</code></pre></figure>

<p>En consecuencia de los nuevos cambios aplicados, se habrá abierto un <em>socket TCP/IP</em> en el puerto <strong>8892</strong> que estará escuchando peticiones en la interfaz <em>loopback</em>, así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/etc/opendkim/keys/iesgn19.es# netstat <span class="nt">-tlnp</span> | egrep opendkim
tcp        0      0 127.0.0.1:8892          0.0.0.0:<span class="k">*</span>               LISTEN      6851/opendkim</code></pre></figure>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería, de manera que todo está listo para enviar un correo al exterior. En este caso, voy a enviar un correo a la dirección <strong>check-auth@verifier.port25.com</strong>, que me responderá con otro correo electrónico mostrando un resumen de los resultados de las pruebas llevadas a cabo sobre el mismo, entre las que se encuentran una comprobación del mecanismo <em>SPF</em> y otra para <em>DKIM</em>.</p>

<p>En mi caso, el resultado obtenido es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">This message is an automatic response from Port25<span class="s1">'s authentication verifier
service at verifier.port25.com.  The service allows email senders to perform
a simple check of various sender authentication mechanisms.  It is provided
free of charge, in the hope that it is useful to the email community.  While
it is not officially supported, we welcome any feedback you may have at
&lt;verifier-feedback@port25.com&gt;.

Thank you for using the verifier,

The Port25 Solutions, Inc. team

==========================================================
Summary of Results
==========================================================
SPF check:          pass
"iprev" check:      pass
DKIM check:         pass

==========================================================
Details:
==========================================================

HELO hostname:  vps.iesgn19.es
Source IP:      51.210.109.246
mail-from:      root@iesgn19.es

----------------------------------------------------------</span></code></pre></figure>

<p>Como se puede apreciar, las directivas <strong>SPF check</strong> y <strong>DKIM check</strong> tienen el valor <strong>pass</strong>, lo que significa que la validación del remitente ha sido efectiva gracias a los registros <em>SPF</em> y <em>DKIM</em> que previamente hemos definido.</p>

<h2 id="comprobación-de-spf">Comprobación de SPF</h2>

<p>Hasta ahora, todas las comprobaciones que se han llevado a cabo sobre los mecanismos <em>SPF</em> y <em>DKIM</em> han sido ejecutadas por los servidores de correo externos que recibían nuestros correos.</p>

<p>Sin embargo, nosotros nos encontramos “expuestos”, al no haber implementado la comprobación de ningún tipo de autenticación de procedencia del correo, de manera que en este caso vamos a comprobar el registro <em>SPF</em> para aquellos correos entrantes.</p>

<p>Para que <strong>Postfix</strong> lleve a cabo la comprobación del registro SPF del dominio origen del correo tendremos que instalar el paquete <strong>postfix-policyd-spf-python</strong>, pues es una funcionalidad extra que vamos a añadir a nuestro servidor de correo y viene empaquetado de una forma ajena. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>postfix-policyd-spf-python</code></pre></figure>

<p>El hecho de haber instalado el nuevo paquete no supone que la comprobación del <em>SPF</em> ya se esté llevando a cabo, ya que para ello necesitamos modificar los ficheros de configuración de <strong>Postfix</strong> para añadir las directivas necesarias. El primero que modificaremos será <strong>/etc/postfix/master.cf</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/master.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que incluir la siguiente directiva:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">policyd-spf  unix  -    n       n       -       0       spawn
  <span class="nv">user</span><span class="o">=</span>policyd-spf <span class="nv">argv</span><span class="o">=</span>/usr/bin/policyd-spf</code></pre></figure>

<p>Gracias a la misma, se ejecutará un proceso en un <em>socket UNIX</em> que será el que se utilice para el análisis del <em>SPF</em>. De otro lado, todavía necesitamos indicarle a nuestro servidor de correo qué debe hacer con aquellos correos que pasen el filtro, realizando una modificación en el fichero <strong>/etc/postfix/main.cf</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/main.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que incluir la siguiente directiva:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">policyd-spf_time_limit <span class="o">=</span> 3600
smtpd_recipient_restrictions <span class="o">=</span> check_policy_service unix:private/policyd-spf</code></pre></figure>

<p>Gracias a la misma, hemos establecido un <em>timeout</em>, así como una restricción de manera que todos los correos entrantes han de pasar el filtro <em>SPF</em> o de lo contrario serán rechazados.</p>

<p>Para verificar que nuestro nuevo mecanismo de autenticación está funcionando correctamente, tendremos que reiniciar el servicio <strong>Postfix</strong> para así aplicar los cambios llevados a cabo, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart postfix</code></pre></figure>

<p>Una vez que el servicio ha sido correctamente reiniciado, he procedido a enviar desde mi correo personal un correo electrónico a <strong>root</strong>, para así verificar que la comprobación del <em>SPF</em> se lleva a cabo tal y como debería.</p>

<p>No considero necesario mostrar el proceso de envío, sino los <em>logs</em> producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">cat</span> /var/log/mail.log
Feb 15 17:09:20 vps postfix/smtpd[7345]: connect from mail-wr1-f51.google.com[209.85.221.51]
Feb 15 17:09:21 vps policyd-spf[7352]: prepend Received-SPF: Pass <span class="o">(</span>mailfrom<span class="o">)</span> <span class="nv">identity</span><span class="o">=</span>mailfrom<span class="p">;</span> client-ip<span class="o">=</span>209.85.221.51<span class="p">;</span> <span class="nv">helo</span><span class="o">=</span>mail-wr1-f51.google.com<span class="p">;</span> envelope-from<span class="o">=</span>avacaferreras@gmail.com<span class="p">;</span> <span class="nv">receiver</span><span class="o">=</span>&lt;UNKNOWN&gt;
Feb 15 17:09:21 vps postfix/smtpd[7345]: 1F3E4E1322: <span class="nv">client</span><span class="o">=</span>mail-wr1-f51.google.com[209.85.221.51]
Feb 15 17:09:21 vps postfix/cleanup[7355]: 1F3E4E1322: message-id<span class="o">=</span>&lt;CANR0p-1iDjvsBA3adavyRhB2uojRDW+yHFF-qFcqYNki-9k4mg@mail.gmail.com&gt;
Feb 15 17:09:21 vps opendkim[6851]: 1F3E4E1322: <span class="nv">s</span><span class="o">=</span>20161025 <span class="nv">d</span><span class="o">=</span>gmail.com SSL
Feb 15 17:09:21 vps postfix/qmgr[7293]: 1F3E4E1322: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>2899, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Feb 15 17:09:21 vps postfix/local[7356]: 1F3E4E1322: <span class="nv">to</span><span class="o">=</span>&lt;root@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span><span class="nb">local</span>, <span class="nv">delay</span><span class="o">=</span>0.39, <span class="nv">delays</span><span class="o">=</span>0.38/0.01/0/0, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>delivered to mailbox<span class="o">)</span>
Feb 15 17:09:21 vps postfix/qmgr[7293]: 1F3E4E1322: removed
Feb 15 17:09:21 vps postfix/smtpd[7345]: disconnect from mail-wr1-f51.google.com[209.85.221.51] <span class="nv">ehlo</span><span class="o">=</span>2 <span class="nv">starttls</span><span class="o">=</span>1 <span class="nv">mail</span><span class="o">=</span>1 <span class="nv">rcpt</span><span class="o">=</span>1 <span class="nv">bdat</span><span class="o">=</span>1 <span class="nv">quit</span><span class="o">=</span>1 <span class="nv">commands</span><span class="o">=</span>7</code></pre></figure>

<p>Como se puede apreciar en la segunda línea del resultado de la ejecución de dicho comando, el resultado del filtro ha sido correcto (<strong>Received-SPF: Pass</strong>), de manera que podemos asegurar que nuestro servidor de correo está teniendo en cuenta los registros <em>SPF</em> de los dominios de los correos entrantes.</p>

<h2 id="configuración-de-antispam">Configuración de antispam</h2>

<p>Los mensajes de correo no deseados (<em>spam</em>) están a la orden del día, de manera que siempre es conveniente aplicar algún que otro filtro que controle la recepción de dicho tipo de correos, para así evitar inundar nuestra bandeja de entrada de correo inútil.</p>

<p>En este caso, vamos a hacer uso de <a href="https://spamassassin.apache.org/">SpamAssassin</a>, un filtro de correo que añadiremos a <strong>Postfix</strong>, cuya principal intención como se puede suponer, es detectar mediante una serie de <em>tests</em> y tratar de una determinada forma el correo <em>spam</em>.</p>

<p>Para el correcto funcionamiento de <strong>SpamAssassin</strong> tendremos que instalar los paquetes <strong>spamassassin</strong> y <strong>spamc</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>spamassassin spamc</code></pre></figure>

<p>En mi caso, una vez finalizada la instalación, el servicio <strong>spamassassin</strong> no se encontraba activo ni habilitado para arrancar junto al sistema, de manera que para solucionarlo ejecutaremos los siguientes comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl start spamassassin
root@vps:~# systemctl <span class="nb">enable </span>spamassassin</code></pre></figure>

<p>Como cualquier otro <em>antispam</em>, <strong>spamassassin</strong> hace uso de una base de datos para realizar los respectivos <em>tests</em> rutinarios cuando recibe un correo, de manera que tendremos que intentar que dicha base de datos se encuentre en todo momento lo más actualizada posible.</p>

<p>Para ello, tendremos que llevar a cabo una pequeña modificación en el fichero <strong>/etc/default/spamassassin</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/default/spamassassin</code></pre></figure>

<p>Dentro del mismo, encontraremos una directiva que tendrá la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">CRON</span><span class="o">=</span>0</code></pre></figure>

<p>Como se puede suponer, tendremos que modificar su valor a <strong>1</strong> para que actualice dicha base de datos una vez al día, que generalmente suele ocurrir durante la noche, ya que es cuando se supone que menos actividad hay, quedando el siguiente resultado final:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">CRON</span><span class="o">=</span>1</code></pre></figure>

<p>El hecho de haber instalado el nuevo paquete no supone que la comprobación del <em>spam</em> ya se esté llevando a cabo, ya que para ello necesitamos modificar el fichero maestro de configuración de <strong>Postfix</strong> para añadir las directivas necesarias, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/master.cf</code></pre></figure>

<p>Dentro del mismo, encontraremos dos directivas que tendrán la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">smtp      inet  n       -       y       -       -       smtpd

submission inet n       -       y       -       -       smtpd</code></pre></figure>

<p>Tendremos que llevar a cabo una pequeña modificación sobre dichas directivas, para así indicar que todo el correo pase por <strong>spamassassin</strong> para llevar a cabo, como es lógico, las comprobaciones necesarias. Además, añadiremos una nueva directiva para el correcto funcionamiento del sistema <em>antispam</em>, quedando de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">smtp      inet  n       -       y       -       -       smtpd
  <span class="nt">-o</span> <span class="nv">content_filter</span><span class="o">=</span>spamassassin

submission inet n       -       y       -       -       smtpd
  <span class="nt">-o</span> <span class="nv">content_filter</span><span class="o">=</span>spamassassin

spamassassin unix -     n       n       -       -       pipe
  <span class="nv">user</span><span class="o">=</span>debian-spamd <span class="nv">argv</span><span class="o">=</span>/usr/bin/spamc <span class="nt">-f</span> <span class="nt">-e</span> /usr/sbin/sendmail <span class="nt">-oi</span> <span class="nt">-f</span> <span class="k">${</span><span class="nv">sender</span><span class="k">}</span> <span class="k">${</span><span class="nv">recipient</span><span class="k">}</span></code></pre></figure>

<p>Toda la configuración referente a <strong>Postfix</strong> ha finalizado, sin embargo, todavía no hemos indicado la forma en la que se tratarán a los correos que se marquen como no deseados.</p>

<p>Para ello, tendremos que modificar el fichero <strong>/etc/spamassassin/local.cf</strong>, cuya configuración puede ser todo lo compleja que deseemos para así afinarla a nuestras necesidades, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/spamassassin/local.cf</code></pre></figure>

<p>En mi caso, me limitaré a modificar el asunto del correo en cuestión para añadirle el encabezado “<strong>****<em>SPAM</em>****</strong>”, de manera que tendré que localizar la directiva con la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># rewrite_header Subject *****SPAM*****</span></code></pre></figure>

<p>Tras ello, tendremos que descomentarla, quedando de la siguiente manera:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">rewrite_header Subject <span class="k">*****</span>SPAM<span class="k">*****</span></code></pre></figure>

<p>Para verificar que nuestro nuevo mecanismo <em>antispam</em> está funcionando correctamente, tendremos que reiniciar los servicios <strong>Postfix</strong> y <strong>spamassassin</strong> para así aplicar los cambios llevados a cabo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart postfix spamassassin</code></pre></figure>

<p>Una vez que los servicios han sido correctamente reiniciados, he procedido a enviar desde mi correo personal un correo electrónico a <strong>root</strong>, para así verificar que la comprobación del <em>spam</em> se lleva a cabo tal y como debería, indicando para ello en el cuerpo del mensaje un texto que se utiliza para comprobar la eficacia de dichos sistemas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">XJS<span class="k">*</span>C4JDBQADN1.NSBN3<span class="k">*</span>2IDNEN<span class="k">*</span>GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL<span class="k">*</span>C.34X</code></pre></figure>

<p>No considero necesario mostrar el proceso de envío, sino los <em>logs</em> producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">cat</span> /var/log/mail.log
Feb 15 17:42:08 vps postfix/smtpd[8886]: connect from mail-wm1-f52.google.com[209.85.128.52]
Feb 15 17:42:08 vps policyd-spf[8894]: prepend Received-SPF: Pass <span class="o">(</span>mailfrom<span class="o">)</span> <span class="nv">identity</span><span class="o">=</span>mailfrom<span class="p">;</span> client-ip<span class="o">=</span>209.85.128.52<span class="p">;</span> <span class="nv">helo</span><span class="o">=</span>mail-wm1-f52.google.com<span class="p">;</span> envelope-from<span class="o">=</span>avacaferreras@gmail.com<span class="p">;</span> <span class="nv">receiver</span><span class="o">=</span>&lt;UNKNOWN&gt;
Feb 15 17:42:08 vps postfix/smtpd[8886]: F3106E1325: <span class="nv">client</span><span class="o">=</span>mail-wm1-f52.google.com[209.85.128.52]
Feb 15 17:42:08 vps postfix/cleanup[8898]: F3106E1325: message-id<span class="o">=</span>&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG<span class="o">=</span>qtg+1VQg@mail.gmail.com&gt;
Feb 15 17:42:08 vps opendkim[6851]: F3106E1325: <span class="nv">s</span><span class="o">=</span>20161025 <span class="nv">d</span><span class="o">=</span>gmail.com SSL
Feb 15 17:42:09 vps postfix/qmgr[8769]: F3106E1325: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>2908, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Feb 15 17:42:09 vps spamd[8772]: spamd: connection from ::1 <span class="o">[</span>::1]:34624 to port 783, fd 5
Feb 15 17:42:09 vps spamd[8772]: spamd: setuid to debian-spamd succeeded
Feb 15 17:42:09 vps spamd[8772]: spamd: processing message &lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG<span class="o">=</span>qtg+1VQg@mail.gmail.com&gt; <span class="k">for </span>debian-spamd:114
Feb 15 17:42:09 vps postfix/smtpd[8886]: disconnect from mail-wm1-f52.google.com[209.85.128.52] <span class="nv">ehlo</span><span class="o">=</span>2 <span class="nv">starttls</span><span class="o">=</span>1 <span class="nv">mail</span><span class="o">=</span>1 <span class="nv">rcpt</span><span class="o">=</span>1 <span class="nv">bdat</span><span class="o">=</span>1 <span class="nv">quit</span><span class="o">=</span>1 <span class="nv">commands</span><span class="o">=</span>7
Feb 15 17:42:09 vps spamd[8772]: spamd: identified spam <span class="o">(</span>999.9/5.0<span class="o">)</span> <span class="k">for </span>debian-spamd:114 <span class="k">in </span>0.2 seconds, 2979 bytes.
Feb 15 17:42:09 vps spamd[8772]: spamd: result: Y 999 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,GTUBE,HTML_MESSAGE,RCVD_IN_MSPIKE_H2,SPF_PASS,TVD_SPACE_RATIO <span class="nv">scantime</span><span class="o">=</span>0.2,size<span class="o">=</span>2979,user<span class="o">=</span>debian-spamd,uid<span class="o">=</span>114,required_score<span class="o">=</span>5.0,rhost<span class="o">=</span>::1,raddr<span class="o">=</span>::1,rport<span class="o">=</span>34624,mid<span class="o">=</span>&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG<span class="o">=</span>qtg+1VQg@mail.gmail.com&gt;,autolearn<span class="o">=</span>no <span class="nv">autolearn_force</span><span class="o">=</span>no
Feb 15 17:42:09 vps postfix/pickup[8768]: 4119BE13D0: <span class="nv">uid</span><span class="o">=</span>114 <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;
Feb 15 17:42:09 vps postfix/cleanup[8898]: 4119BE13D0: message-id<span class="o">=</span>&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG<span class="o">=</span>qtg+1VQg@mail.gmail.com&gt;
Feb 15 17:42:09 vps postfix/pipe[8899]: F3106E1325: <span class="nv">to</span><span class="o">=</span>&lt;root@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span>spamassassin, <span class="nv">delay</span><span class="o">=</span>0.36, <span class="nv">delays</span><span class="o">=</span>0.13/0/0/0.23, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>delivered via spamassassin service<span class="o">)</span>
Feb 15 17:42:09 vps postfix/qmgr[8769]: F3106E1325: removed
Feb 15 17:42:09 vps postfix/qmgr[8769]: 4119BE13D0: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>6217, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Feb 15 17:42:09 vps postfix/local[8903]: 4119BE13D0: <span class="nv">to</span><span class="o">=</span>&lt;root@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span><span class="nb">local</span>, <span class="nv">delay</span><span class="o">=</span>0.01, <span class="nv">delays</span><span class="o">=</span>0.01/0/0/0, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>delivered to mailbox<span class="o">)</span>
Feb 15 17:42:09 vps postfix/qmgr[8769]: 4119BE13D0: removed
Feb 15 17:42:09 vps spamd[8695]: prefork: child states: II</code></pre></figure>

<p>Como se puede apreciar en el resultado de la ejecución de dicho comando, primero se ha comprobado el <em>SPF</em> del remitente y una vez que se ha validado, el mensaje ha pasado a <strong>spamd</strong>, el cuál ha determinado que el mensaje es <em>spam</em> (<strong>identified spam</strong>), asignándole a su vez una puntuación (que en este caso es la más alta, ya que el mensaje está pensado para ello). A pesar de ello y gracias a la configuración establecida, el correo ha sido entregado en el buzón de igual forma.</p>

<p>Para vericarlo, volveremos a hacer uso de la utilidad de línea de comandos, recibiendo por pantalla el siguiente resultado:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# mail
Mail version 8.1.2 01/15/2001.  Type ? <span class="k">for </span>help.
<span class="s2">"/var/mail/root"</span>: 6 messages 2 new 6 unread
 U  1 root@iesgn19.es    Thu Jan 21 09:33   23/693   Cron &lt;root@vps&gt; <span class="nb">date
 </span>U  2 root@iesgn19.es    Thu Jan 21 09:34   23/693   Cron &lt;root@vps&gt; <span class="nb">date
 </span>U  3 root@iesgn19.es    Thu Jan 21 09:35   23/693   Cron &lt;root@vps&gt; <span class="nb">date
 </span>U  4 avacaferreras@gma  Mon Feb 15 17:09   61/3136  Prueba SPF
<span class="o">&gt;</span>N  5 auth-results@veri  Mon Feb 15 17:11  232/9769  Authentication Report
 N  6 avacaferreras@gma  Mon Feb 15 17:42  128/6215  <span class="k">*****</span>SPAM<span class="k">*****</span> Prueba SPAM</code></pre></figure>

<p>Efectivamente, el último mensaje recibido ha sido marcado como <em>spam</em> por <strong>spamassassin</strong> y por tanto, se le ha añadido la cabecera “<em><strong>***SPAM</strong>***</em>” al asunto de dicho mensaje, para identificarlo fácilmente.</p>

<p>Esta medida es una de las menos restrictivas, ya que lo único que hace es marcar los mensajes detectados como <em>spam</em>, aunque en caso de así necesitarlo, podríamos descartar directamente dichos correos para que no se entregasen en el buzón.</p>

<h2 id="configuración-de-antivirus">Configuración de antivirus</h2>

<p>Los mensajes de correo con virus están a la orden del día, de manera que siempre es conveniente aplicar algún que otro filtro que controle la recepción de dicho tipo de correos, para así evitar inundar nuestra bandeja de entrada de correo inútil.</p>

<p>En este caso, vamos a hacer uso de <a href="https://www.clamav.net/">ClamAV</a>, un filtro de correo que añadiremos a <strong>Postfix</strong>, cuya principal intención como se puede suponer, es detectar mediante una serie de <em>tests</em> y tratar de una determinada forma el correo infectado.</p>

<p>Para el correcto funcionamiento de <strong>ClamAV</strong> tendremos que instalar los paquetes <strong>clamsmtp</strong> y <strong>clamav-daemon</strong>, así como una serie de paquetería que permite tratar los ficheros comprimidos, para así poder detectar también virus en los mismos, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>clamsmtp clamav-daemon arc arj bzip2 cabextract lzop nomarch p7zip pax tnef unrar-free unzip</code></pre></figure>

<p>El paquete <strong>clamsmtp</strong> ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto un <em>socket TCP/IP</em> en el puerto <strong>25</strong> que estará escuchando peticiones en la interfaz <em>loopback</em>, así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tlnp</span> | egrep clamsmtp
tcp        0      0 127.0.0.1:10026         0.0.0.0:<span class="k">*</span>               LISTEN      11870/clamsmtpd</code></pre></figure>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería.</p>

<p>En mi caso, una vez finalizada la instalación, el demonio <strong>clamav-daemon</strong> no se encontraba activo, de manera que para solucionarlo haremos uso del siguiente comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl start clamav-daemon</code></pre></figure>

<p>El hecho de haber instalado el nuevo paquete no supone que la comprobación de los virus ya se esté llevando a cabo, ya que para ello necesitamos modificar los ficheros de configuración de <strong>Postfix</strong> para añadir las directivas necesarias. El primero que modificaremos será <strong>/etc/postfix/master.cf</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/master.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que incluir las siguientes directivas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">scan unix -       -       n       -       16       smtp
  <span class="nt">-o</span> <span class="nv">smtp_data_done_timeout</span><span class="o">=</span>1200
  <span class="nt">-o</span> <span class="nv">smtp_send_xforward_command</span><span class="o">=</span><span class="nb">yes</span>
  <span class="nt">-o</span> <span class="nv">disable_dns_lookups</span><span class="o">=</span><span class="nb">yes
</span>127.0.0.1:10025 inet n       -       n       -       16       smtpd
  <span class="nt">-o</span> <span class="nv">content_filter</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">local_recipient_maps</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">relay_recipient_maps</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_restriction_classes</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_client_restrictions</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_helo_restrictions</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_sender_restrictions</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_recipient_restrictions</span><span class="o">=</span>permit_mynetworks,reject
  <span class="nt">-o</span> <span class="nv">mynetworks_style</span><span class="o">=</span>host
  <span class="nt">-o</span> <span class="nv">smtpd_authorized_xforward_hosts</span><span class="o">=</span>127.0.0.0/8</code></pre></figure>

<p>Gracias a la misma, habremos indicado que todo el correo pase por <strong>clamav</strong> para llevar a cabo, como es lógico, las comprobaciones necesarias. De otro lado, todavía necesitamos indicarle a nuestro servidor de correo dónde debe comunicarse con el servicio encargado de llevar a cabo las comprobaciones sobre dichos correos, realizando una modificación en el fichero <strong>/etc/postfix/main.cf</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/main.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que incluir la siguiente directiva:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">content_filter <span class="o">=</span> scan:127.0.0.1:10026</code></pre></figure>

<p>Para verificar que nuestro nuevo mecanismo <em>antivirus</em> está funcionando correctamente, tendremos que reiniciar el servicio <strong>Postfix</strong> para así aplicar los cambios llevados a cabo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart postfix</code></pre></figure>

<p>Una vez que el servicio ha sido correctamente reiniciado, he procedido a enviar desde mi correo personal un correo electrónico a <strong>root</strong>, para así verificar que la comprobación de los virus se lleva a cabo tal y como debería, indicando para ello en el cuerpo del mensaje un texto que se utiliza para comprobar la eficacia de dichos sistemas:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">X5O!P%@AP[4<span class="se">\P</span>ZX54<span class="o">(</span>P^<span class="o">)</span>7CC<span class="o">)</span>7<span class="o">}</span><span class="nv">$EICAR</span><span class="nt">-STANDARD-ANTIVIRUS-TEST-FILE</span><span class="o">!</span><span class="nv">$H</span>+H<span class="k">*</span></code></pre></figure>

<p>No considero necesario mostrar el proceso de envío, sino los <em>logs</em> producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Feb 15 18:15:49 vps postfix/smtpd[12662]: connect from mail-wm1-f54.google.com[209.85.128.54]
Feb 15 18:15:49 vps policyd-spf[12665]: prepend Received-SPF: Pass <span class="o">(</span>mailfrom<span class="o">)</span> <span class="nv">identity</span><span class="o">=</span>mailfrom<span class="p">;</span> client-ip<span class="o">=</span>209.85.128.54<span class="p">;</span> <span class="nv">helo</span><span class="o">=</span>mail-wm1-f54.google.com<span class="p">;</span> envelope-from<span class="o">=</span>avacaferreras@gmail.com<span class="p">;</span> <span class="nv">receiver</span><span class="o">=</span>&lt;UNKNOWN&gt;
Feb 15 18:15:49 vps postfix/smtpd[12662]: E4F3CE13D1: <span class="nv">client</span><span class="o">=</span>mail-wm1-f54.google.com[209.85.128.54]
Feb 15 18:15:49 vps postfix/cleanup[12669]: E4F3CE13D1: message-id<span class="o">=</span>&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX<span class="o">=</span>5CzM3PBg@mail.gmail.com&gt;
Feb 15 18:15:49 vps opendkim[6851]: E4F3CE13D1: <span class="nv">s</span><span class="o">=</span>20161025 <span class="nv">d</span><span class="o">=</span>gmail.com SSL
Feb 15 18:15:49 vps postfix/qmgr[12612]: E4F3CE13D1: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>3230, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Feb 15 18:15:49 vps spamd[8772]: spamd: connection from ::1 <span class="o">[</span>::1]:34804 to port 783, fd 5
Feb 15 18:15:49 vps spamd[8772]: spamd: setuid to debian-spamd succeeded
Feb 15 18:15:49 vps spamd[8772]: spamd: processing message &lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX<span class="o">=</span>5CzM3PBg@mail.gmail.com&gt; <span class="k">for </span>debian-spamd:114
Feb 15 18:15:49 vps postfix/smtpd[12662]: disconnect from mail-wm1-f54.google.com[209.85.128.54] <span class="nv">ehlo</span><span class="o">=</span>2 <span class="nv">starttls</span><span class="o">=</span>1 <span class="nv">mail</span><span class="o">=</span>1 <span class="nv">rcpt</span><span class="o">=</span>1 <span class="nv">bdat</span><span class="o">=</span>1 <span class="nv">quit</span><span class="o">=</span>1 <span class="nv">commands</span><span class="o">=</span>7
Feb 15 18:15:50 vps spamd[8772]: spamd: clean message <span class="o">(</span><span class="nt">-0</span>.1/5.0<span class="o">)</span> <span class="k">for </span>debian-spamd:114 <span class="k">in </span>0.2 seconds, 3297 bytes.
Feb 15 18:15:50 vps spamd[8772]: spamd: result: <span class="nb">.</span> 0 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,HTML_MESSAGE,RCVD_IN_MSPIKE_H2,SPF_PASS,TVD_SPACE_RATIO <span class="nv">scantime</span><span class="o">=</span>0.2,size<span class="o">=</span>3297,user<span class="o">=</span>debian-spamd,uid<span class="o">=</span>114,required_score<span class="o">=</span>5.0,rhost<span class="o">=</span>::1,raddr<span class="o">=</span>::1,rport<span class="o">=</span>34804,mid<span class="o">=</span>&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX<span class="o">=</span>5CzM3PBg@mail.gmail.com&gt;,autolearn<span class="o">=</span>ham <span class="nv">autolearn_force</span><span class="o">=</span>no
Feb 15 18:15:50 vps postfix/pickup[12611]: 34FC3E14DC: <span class="nv">uid</span><span class="o">=</span>114 <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;
Feb 15 18:15:50 vps postfix/cleanup[12669]: 34FC3E14DC: message-id<span class="o">=</span>&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX<span class="o">=</span>5CzM3PBg@mail.gmail.com&gt;
Feb 15 18:15:50 vps postfix/pipe[12670]: E4F3CE13D1: <span class="nv">to</span><span class="o">=</span>&lt;root@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span>spamassassin, <span class="nv">delay</span><span class="o">=</span>0.32, <span class="nv">delays</span><span class="o">=</span>0.08/0/0/0.24, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>delivered via spamassassin service<span class="o">)</span>
Feb 15 18:15:50 vps postfix/qmgr[12612]: E4F3CE13D1: removed
Feb 15 18:15:50 vps opendkim[6851]: 34FC3E14DC: <span class="nv">s</span><span class="o">=</span>20161025 <span class="nv">d</span><span class="o">=</span>gmail.com SSL
Feb 15 18:15:50 vps spamd[8695]: prefork: child states: II
Feb 15 18:15:50 vps postfix/qmgr[12612]: 34FC3E14DC: <span class="nv">from</span><span class="o">=</span>&lt;avacaferreras@gmail.com&gt;, <span class="nv">size</span><span class="o">=</span>3806, <span class="nv">nrcpt</span><span class="o">=</span>1 <span class="o">(</span>queue active<span class="o">)</span>
Feb 15 18:15:50 vps clamsmtpd: 100007: accepted connection from: 127.0.0.1
Feb 15 18:15:50 vps postfix/smtpd[12679]: connect from localhost[127.0.0.1]
Feb 15 18:15:50 vps postfix/smtpd[12679]: 4BCE6E13D1: <span class="nv">client</span><span class="o">=</span>localhost[127.0.0.1]
Feb 15 18:15:50 vps postfix/smtp[12677]: 34FC3E14DC: <span class="nv">to</span><span class="o">=</span>&lt;root@iesgn19.es&gt;, <span class="nv">relay</span><span class="o">=</span>127.0.0.1[127.0.0.1]:10026, <span class="nv">delay</span><span class="o">=</span>0.1, <span class="nv">delays</span><span class="o">=</span>0.05/0/0.04/0, <span class="nv">dsn</span><span class="o">=</span>2.0.0, <span class="nv">status</span><span class="o">=</span>sent <span class="o">(</span>250 Virus Detected<span class="p">;</span> Discarded Email<span class="o">)</span>
Feb 15 18:15:50 vps postfix/qmgr[12612]: 34FC3E14DC: removed
Feb 15 18:15:50 vps clamsmtpd: 100007: <span class="nv">from</span><span class="o">=</span>avacaferreras@gmail.com, <span class="nv">to</span><span class="o">=</span>root@iesgn19.es, <span class="nv">status</span><span class="o">=</span>VIRUS:Eicar-Signature
Feb 15 18:15:50 vps postfix/smtpd[12679]: disconnect from localhost[127.0.0.1] <span class="nv">ehlo</span><span class="o">=</span>1 <span class="nv">xforward</span><span class="o">=</span>1 <span class="nv">mail</span><span class="o">=</span>1 <span class="nv">rcpt</span><span class="o">=</span>1 <span class="nv">rset</span><span class="o">=</span>1 <span class="nv">quit</span><span class="o">=</span>1 <span class="nv">commands</span><span class="o">=</span>6</code></pre></figure>

<p>Como se puede apreciar en el resultado de la ejecución de dicho comando, primero se ha comprobado el <em>SPF</em> del remitente y una vez que se ha validado, el mensaje ha pasado a <strong>spamd</strong>, el cuál ha determinado que el mensaje tampoco es <em>spam</em> (<strong>clean message</strong>), pasando por último a actuar <strong>clamsmtpd</strong>, que finalmente ha descartado el mensaje al detectar que se trata de un virus (<strong>250 Virus Detected; Discarded Email</strong>).</p>

<h2 id="configuración-de-maildir">Configuración de Maildir</h2>

<p>Aunque no lo hayamos mencionado con profundidad hasta ahora, el tipo de buzón que por defecto utiliza <strong>Postfix</strong> es <strong>mbox</strong>, aquel que almacena todos los mensajes en un fichero y que se utiliza para el protocolo <em>POP3</em>, en el que se descargan todos los correos desde el servidor.</p>

<p>Sin embargo, la idea de este artículo es dejar de hacer uso del servidor de correo de forma local, y empezar a utilizar un cliente que se conecte al mismo mediante el protocolo <em>SMTP</em> para el envío de correos y mediante el protocolo <em>IMAP</em> para la sincronización de los mismos, dadas las ventajas que éste último nos ofrece.</p>

<p>La característica del protocolo <em>IMAP</em> es que únicamente permite la sincronización con los buzones de tipo <strong>Maildir</strong>, aquel que almacena los mensajes en un directorio con sus correspondientes subdirectorios.</p>

<p>Para llevar a cabo el cambio de buzón de tipo <strong>mbox</strong> a uno de tipo <strong>Maildir</strong>, tendremos que modificar el fichero de configuración principal de <strong>Postfix</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/main.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que añadir una línea de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">home_mailbox <span class="o">=</span> Maildir/</code></pre></figure>

<p>Gracias a la misma, una vez que reiniciemos el servicio y los cambios surtan efecto, los nuevos mensajes de correo recibidos se almacenarán de forma automática en un directorio de nombre <strong>Maildir/</strong> en el directorio personal de cada uno de los usuarios existentes en la máquina, de manera que reiniciaremos el servicio haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart postfix</code></pre></figure>

<p>A partir de este momento, no podremos leer los correos recibidos a través de la herramienta de línea de comandos <code class="language-plaintext highlighter-rouge">mail</code>, ya que no soporta de forma nativa este tipo de buzón. Para solventarlo, tendremos que instalar un nuevo cliente de línea de comandos, como por ejemplo <strong>mutt</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>mutt</code></pre></figure>

<p>A pesar de haber instalado el nuevo cliente, este no se encuentra todavía configurado para buscar los mensajes de correo dentro de <strong>~/Maildir/</strong>, pues tendremos que indicarle dicho directorio de forma explícita en un fichero de nombre <strong>~/.muttrc</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano ~/.muttrc</code></pre></figure>

<p>Dentro del mismo, introduciremos el siguiente contenido:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nb">set </span><span class="nv">mbox_type</span><span class="o">=</span>Maildir
<span class="nb">set </span><span class="nv">folder</span><span class="o">=</span><span class="s2">"~/Maildir"</span>
<span class="nb">set </span><span class="nv">mask</span><span class="o">=</span><span class="s2">"!^</span><span class="se">\\</span><span class="s2">.[^.]"</span>
<span class="nb">set </span><span class="nv">mbox</span><span class="o">=</span><span class="s2">"~/Maildir"</span>
<span class="nb">set </span><span class="nv">record</span><span class="o">=</span><span class="s2">"+.Sent"</span>
<span class="nb">set </span><span class="nv">postponed</span><span class="o">=</span><span class="s2">"+.Drafts"</span>
<span class="nb">set </span><span class="nv">spoolfile</span><span class="o">=</span><span class="s2">"~/Maildir"</span></code></pre></figure>

<p><strong>Nota</strong>: En caso de haber indicado un nombre de directorio diferente a <strong>~/Maildir</strong> en la configuración de <strong>Postfix</strong>, deberá modificarse la ruta del mismo a la hora de establecer el valor de las directivas mostradas.</p>

<p>Toda la configuración necesaria para hacer uso del nuevo tipo de buzón ha finalizado, de manera que he procedido a enviar desde mi correo personal un correo electrónico a <strong>root</strong>, para así verificar que los nuevos correos entrantes se sitúan en un directorio de nombre <strong>~/Maildir</strong>, tal y como deberían.</p>

<p>No considero necesario mostrar el proceso de envío, sino el nuevo contenido del directorio mencionado producido por dicha recepción, concretamente en el subdirectorio de nombre <strong>new/</strong>, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">ls</span> <span class="nt">-l</span> Maildir/new/
total 4
<span class="nt">-rw-------</span> 1 root root 3548 Feb 16 08:20 1613460040.V801I42733M857089.vps</code></pre></figure>

<p>Como era de esperar y como resultado de la recepción de un nuevo correo electrónico, ha aparecido un nuevo fichero en el correspondiente directorio del usuario <strong>root</strong> que hace referencia al mismo.</p>

<p>Tal y como ya hemos mencionado, no podremos visualizar el contenido de dicho correo con la herramienta <code class="language-plaintext highlighter-rouge">mail</code>, sino que procederemos a usar el nuevo cliente de línea de comandos <code class="language-plaintext highlighter-rouge">mutt</code> para ello, ejecutando por lo tanto el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# mutt</code></pre></figure>

<p>Tras ello, se nos mostrará lo siguiente por pantalla:</p>

<p><img src="https://i.ibb.co/qW1QGt2/mutt2.jpg" alt="mutt1" title="Mutt" /></p>

<p>Efectivamente, únicamente se ha mostrado un correo electrónico con asunto “<strong>Prueba mutt</strong>”, correspondiente al único fichero existente en el directorio <strong>~/Maildir/new/</strong>, de manera que pulsaremos <strong>INTRO</strong> para visualizar su contenido y así verificar su correcto funcionamiento:</p>

<p><img src="https://i.ibb.co/T0fS1yC/mutt3.jpg" alt="mutt2" title="Mutt" /></p>

<p>Como era de esperar, el contenido del correo electrónico recibido ha sido correctamente mostrado, por lo que podemos concluir que tanto el nuevo tipo de buzón como el nuevo cliente de línea de comandos están funcionando tal y como deberían.</p>

<h2 id="recepción-de-correos-desde-el-cliente">Recepción de correos desde el cliente</h2>

<p>Como previamente hemos mencionado, si utilizamos un cliente de correo (<em>MUA</em>) externo para leer el correo guardado en el servidor de correos podemos usar dos protocolos de comunicación:</p>

<ul>
  <li>
    <p><strong>POP3 (<em>Post Office Protocol</em>)</strong>: Protocolo para recuperar correos electrónicos de un <em>MDA</em>. Su principal característica es que se descargan todos los correos.</p>
  </li>
  <li>
    <p><strong>IMAP (<em>Internet Message Access Protocol</em>)</strong>: Protocolo para recuperar correos electrónicos de un <em>MDA</em>. A diferencia del anterior, se sincroniza el estado de los correos entre el servidor y el cliente.</p>
  </li>
</ul>

<p>En nuestro caso, vamos a trabajar con <em>IMAP</em>, que es el protocolo actualmente utilizado para poder leer nuestro correo desde distintos clientes de correos al no descargar todos los correos del buzón, como ocurre con <em>POP3</em>.</p>

<p>Además, por seguridad, es recomendable que la conexión a través de estos protocolos, sea cuál sea el que hayamos elegido, se establezca de forma autenticada y cifrada. Por defecto, el protocolo <em>IMAP</em> hace uso de puerto <strong>143/TCP</strong> y lleva a cabo una conexión autenticada, sin embargo, no se encuentra cifrada. Para cifrar dicha comunicación, podemos acudir a las siguientes alternativas:</p>

<ul>
  <li>
    <p><strong>IMAP con STARTTLS</strong>: STARTTLS transforma una conexión insegura en una segura mediante el uso de SSL/TLS, consiguiendo por tanto tener una conexión cifrada a través del puerto <strong>143/TCP</strong>.</p>
  </li>
  <li>
    <p><strong>IMAPS</strong>: Versión segura del protocolo <em>IMAP</em> que hace uso del puerto <strong>993/TCP</strong>.</p>
  </li>
</ul>

<p>Como es lógico, necesitaremos además un servicio como <strong>dovecot</strong> que actúe como servidor <em>IMAP</em>, por lo que procederemos a su instalación haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>dovecot-imapd</code></pre></figure>

<p>El paquete <strong>dovecot-imapd</strong> ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto dos <em>sockets TCP/IP</em> en los puertos por defecto de los protocolos <em>IMAP</em> e <em>IMAPS</em> (<strong>143</strong> y <strong>993</strong>) que estará escuchando peticiones en todas las interfaces de la máquina (<strong>0.0.0.0</strong>), así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tlnp</span> | egrep dovecot
tcp        0      0 0.0.0.0:993             0.0.0.0:<span class="k">*</span>               LISTEN      19744/dovecot
tcp        0      0 0.0.0.0:143             0.0.0.0:<span class="k">*</span>               LISTEN      19744/dovecot
tcp6       0      0 :::993                  :::<span class="k">*</span>                    LISTEN      19744/dovecot
tcp6       0      0 :::143                  :::<span class="k">*</span>                    LISTEN      19744/dovecot</code></pre></figure>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería. Sin embargo, como bien sabemos, si queremos hacer uso de un protocolo cifrado, necesitaremos generar un certificado firmado por una autoridad certificadora de considerable reputación, así que en mi caso, he recurrido una vez más a <strong>Let’s Encrypt</strong>, siguiendo para ello los pasos indicados en el artículo <a href="https://www.alvarovf.com/seguridad/vps/2020/11/30/configuracion-https-nginx.html">Configuración de HTTPS en Nginx</a>, pues mencionar aquí el proceso se saldría completamente del objetivo del <em>post</em>.</p>

<p>Sin entrar en demasiado detalle, el funcionamiento del protocolo <em>IMAPS</em> es muy similar al del protocolo <em>HTTPS</em>, ya que contaremos con una clave privada y una clave pública (certificado), siendo esta última la que se envía al cliente que trata de realizar la conexión para así ponerse de acuerdo en la clave simétrica que se va a utilizar para cifrar la conexión, pues mantener en todo momento una conexión cifrada asimétricamente sería muy costoso computacionalmente.</p>

<p>Cuando nuestro certificado para el nombre de dominio <strong>mail.iesgn19.es</strong> haya sido correctamente generado, todo estará listo para configurar <strong>dovecot</strong> para que haga uso de los mismos, de manera que comenzaremos por modificar el fichero <strong>/etc/dovecot/conf.d/10-ssl.conf</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/dovecot/conf.d/10-ssl.conf</code></pre></figure>

<p>Dentro del mismo, tendremos que localizar las directivas que por defecto tienen la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">ssl_cert <span class="o">=</span> &lt;/etc/dovecot/private/dovecot.pem
ssl_key <span class="o">=</span> &lt;/etc/dovecot/private/dovecot.key</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>ssl_cert</strong>: Indicamos la ruta del certificado del servidor firmado por la <em>CA</em>. En este caso, <strong>/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem</strong>.</li>
  <li><strong>ssl_key</strong>: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, <strong>/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem</strong>.</li>
</ul>

<p>El resultado final, tras llevar a cabo las correspondientes modificaciones sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">ssl_cert <span class="o">=</span> &lt;/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem
ssl_key <span class="o">=</span> &lt;/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem</code></pre></figure>

<p>A pesar de haber indicado ya el certificado que ha de usarse para la conexión cifrada, el servicio no se encuentra todavía configurado para buscar los mensajes de correo dentro de <strong>~/Maildir/</strong> para su correspondiente sincronización con el cliente, pues tendremos que indicarle dicho directorio de forma explícita en el fichero de nombre <strong>/etc/dovecot/conf.d/10-mail.conf</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/dovecot/conf.d/10-mail.conf</code></pre></figure>

<p>Dentro del mismo, tendremos que localizar la directiva que por defecto tenga la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">mail_location <span class="o">=</span> mbox:~/mail:INBOX<span class="o">=</span>/var/mail/%u</code></pre></figure>

<p>En nuestro caso, al estar haciendo uso de buzones del tipo <strong>Maildir</strong>, tendremos que modificar dicha directiva para que finalmente tenga el siguiente valor:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">mail_location <span class="o">=</span> maildir:~/Maildir</code></pre></figure>

<p>Nuestra configuración del servicio <strong>dovecot</strong> para hacer uso del protocolo <em>IMAP</em> ha finalizado, de manera que tendremos que reiniciar dicho servicio para que los cambios llevados a cabo surtan efecto, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart dovecot</code></pre></figure>

<p>En lugar de llevar a cabo una prueba de dicho protocolo, vamos a realizar también las configuraciones necesarias para enviar correos desde un cliente, de manera que posteriormente realizaremos todos los <em>tests</em> oportunos de manera conjunta.</p>

<h2 id="envío-de-correos-desde-el-cliente">Envío de correos desde el cliente</h2>

<p>Como previamente hemos mencionado, para el envío de correos entre <em>MTA</em> se utiliza el protocolo <em>SMTP</em> en el puerto <strong>25/TCP</strong>, sin embargo, si vamos a utilizar un cliente remoto para el envío de correos, se utilizan dos opciones distintas:</p>

<ul>
  <li><strong>ESMTP + STARTTLS (<em>Enhanced Simple Mail Transfer Protocol</em>)</strong>: STARTTLS transforma una conexión insegura en una segura mediante el uso de SSL/TLS, consiguiendo por tanto tener una conexión cifrada a través del puerto <strong>587/TCP</strong>.</li>
</ul>

<p>Dicho puerto es conocido como puerto de <em>submission</em> o presentación. Al abrir este puerto, <strong>Postfix</strong> esta funcionando como <em>MSA</em> (<em>Mail Submission Agent</em>) que recibe mensajes de correo electrónico desde un <em>MUA</em> (<em>Mail User Agent</em>) y coopera con un <em>MTA</em> (<em>Mail Transport Agent</em>) para entregar el correo.</p>

<p>Tenemos que conseguir que la comunicación que se establece desde el cliente al servidor sea autenticada. Para ello, utilizamos <em>SASL</em> (<em>Simple Authentication and Security Layer</em>), un <em>framework</em> para autenticación y autorización en protocolos de Internet. Para realizar la autenticación vamos a usar <strong>dovecot</strong> (que ya tiene un mecanismo de autenticación).</p>

<p>De otro lado, tenemos que conseguir además que la comunicación sea cifrada, para ello vamos a utilizar <em>STARTTLS</em> que nos permite que utilizando el mismo puerto (<strong>587/TCP</strong>), la conexión sea cifrada.</p>

<ul>
  <li><strong>SMTPS (<em>Simple Mail Transfer Protocol Secure</em>)</strong>: Protocolo para conseguir el cifrado de la comunicación entre el cliente y el servidor. Utiliza el puerto <strong>465/TCP</strong>. No es una extensión de <em>SMTP</em>. Es muy parecido a <em>HTTPS</em>.</li>
</ul>

<p>En este caso, vamos a reutilizar el certificado generado en el anterior apartado para cifrar la conexión <em>SMTP</em> entre el cliente y el servidor, de manera que todo estará listo para configurar <strong>postfix</strong> para que haga uso de los mismos, de manera que comenzaremos por modificar el fichero <strong>/etc/postfix/main.cf</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/main.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que localizar las directivas que por defecto tienen la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">smtpd_tls_cert_file</span><span class="o">=</span>/etc/ssl/certs/ssl-cert-snakeoil.pem
<span class="nv">smtpd_tls_key_file</span><span class="o">=</span>/etc/ssl/private/ssl-cert-snakeoil.key</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>smtpd_tls_cert_file</strong>: Indicamos la ruta del certificado del servidor firmado por la <em>CA</em>. En este caso, <strong>/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem</strong>.</li>
  <li><strong>smtpd_tls_key_file</strong>: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, <strong>/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem</strong>.</li>
</ul>

<p>Además de indicar las rutas de los determinados ficheros, tendremos que añadir una serie de directivas para el correcto funcionamiento del sistema de autenticación por parte de <strong>dovecot</strong>. El resultado final, tras llevar a cabo las correspondientes modificaciones sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">smtpd_tls_cert_file</span><span class="o">=</span>/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem
<span class="nv">smtpd_tls_key_file</span><span class="o">=</span>/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem

smtpd_sasl_auth_enable <span class="o">=</span> <span class="nb">yes
</span>smtpd_sasl_type <span class="o">=</span> dovecot
smtpd_sasl_path <span class="o">=</span> private/auth
smtpd_sasl_authenticated_header <span class="o">=</span> <span class="nb">yes
</span>broken_sasl_auth_clients <span class="o">=</span> <span class="nb">yes</span></code></pre></figure>

<p>A pesar de haber indicado ya el certificado que ha de usarse para la conexión cifrada, el servicio no se encuentra todavía configurado para utilizar los puertos <strong>587/TCP</strong> y <strong>465/TCP</strong>, pues tendremos que indicárselo de forma explícita en el fichero de nombre <strong>/etc/postfix/master.cf</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/postfix/master.cf</code></pre></figure>

<p>Dentro del mismo, tendremos que buscar las directivas <strong>submission</strong> y <strong>smtps</strong> y descomentarlas al completo, quedando de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">submission inet n       -       y       -       -       smtpd
  <span class="nt">-o</span> <span class="nv">content_filter</span><span class="o">=</span>spamassassin
  <span class="nt">-o</span> <span class="nv">syslog_name</span><span class="o">=</span>postfix/submission
  <span class="nt">-o</span> <span class="nv">smtpd_tls_security_level</span><span class="o">=</span>encrypt
  <span class="nt">-o</span> <span class="nv">smtpd_sasl_auth_enable</span><span class="o">=</span><span class="nb">yes</span>
  <span class="nt">-o</span> <span class="nv">smtpd_tls_auth_only</span><span class="o">=</span><span class="nb">yes</span>
  <span class="nt">-o</span> <span class="nv">smtpd_reject_unlisted_recipient</span><span class="o">=</span>no
  <span class="nt">-o</span> <span class="nv">smtpd_client_restrictions</span><span class="o">=</span><span class="nv">$mua_client_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_helo_restrictions</span><span class="o">=</span><span class="nv">$mua_helo_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_sender_restrictions</span><span class="o">=</span><span class="nv">$mua_sender_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_recipient_restrictions</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_relay_restrictions</span><span class="o">=</span>permit_sasl_authenticated,reject
  <span class="nt">-o</span> <span class="nv">milter_macro_daemon_name</span><span class="o">=</span>ORIGINATING
smtps     inet  n       -       y       -       -       smtpd
  <span class="nt">-o</span> <span class="nv">syslog_name</span><span class="o">=</span>postfix/smtps
  <span class="nt">-o</span> <span class="nv">smtpd_tls_wrappermode</span><span class="o">=</span><span class="nb">yes</span>
  <span class="nt">-o</span> <span class="nv">smtpd_sasl_auth_enable</span><span class="o">=</span><span class="nb">yes</span>
  <span class="nt">-o</span> <span class="nv">smtpd_reject_unlisted_recipient</span><span class="o">=</span>no
  <span class="nt">-o</span> <span class="nv">smtpd_client_restrictions</span><span class="o">=</span><span class="nv">$mua_client_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_helo_restrictions</span><span class="o">=</span><span class="nv">$mua_helo_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_sender_restrictions</span><span class="o">=</span><span class="nv">$mua_sender_restrictions</span>
  <span class="nt">-o</span> <span class="nv">smtpd_recipient_restrictions</span><span class="o">=</span>
  <span class="nt">-o</span> <span class="nv">smtpd_relay_restrictions</span><span class="o">=</span>permit_sasl_authenticated,reject
  <span class="nt">-o</span> <span class="nv">milter_macro_daemon_name</span><span class="o">=</span>ORIGINATING</code></pre></figure>

<p>Si recordamos, a la hora de especificarle la ruta de los ficheros del certificado a <strong>Postfix</strong>, hemos introducido las directivas necesarias para que <strong>dovecot</strong> pueda realizar la autenticación, sin embargo, todavía faltaría indicarle a <strong>dovecot</strong> cómo tiene que realizar dicha autenticación, de manera que procederemos a modificar el fichero <strong>/etc/dovecot/conf.d/10-master.conf</strong> para así poder interconectar ambos servicios, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/dovecot/conf.d/10-master.conf</code></pre></figure>

<p>Dentro del mismo, tendremos que localizar la directiva que por defecto tenga la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">unix_listener auth-userdb <span class="o">{</span>
<span class="o">}</span></code></pre></figure>

<p>En este caso, la ruta del <em>socket UNIX</em> que se utilizará para la comunicación entre ambos servicios no es correcta, ni tampoco lo son los permisos asignados, de manera que tendremos que modificarla para que finalmente tenga el siguiente aspecto:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">unix_listener /var/spool/postfix/private/auth <span class="o">{</span>
  mode <span class="o">=</span> 0666
<span class="o">}</span></code></pre></figure>

<p>Nuestra configuración de los servicios <strong>Postfix</strong> y <strong>dovecot</strong> para hacer uso del protocolo <em>SMTP</em> de forma segura ha finalizado, de manera que tendremos que reiniciar dichos servicios para que los cambios llevados a cabo surtan efecto, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart postfix dovecot</code></pre></figure>

<p>En consecuencia de los nuevos cambios aplicados, se habrán abierto dos <em>sockets TCP/IP</em> en los puertos <strong>587</strong> y <strong>465</strong> que estarán escuchando peticiones en todas las interfaces de la máquina (<strong>0.0.0.0</strong>), así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tln</span> | egrep <span class="s1">'\(587|465)'</span>
tcp        0      0 0.0.0.0:587             0.0.0.0:<span class="k">*</span>               LISTEN
tcp        0      0 0.0.0.0:465             0.0.0.0:<span class="k">*</span>               LISTEN
tcp6       0      0 :::587                  :::<span class="k">*</span>                    LISTEN
tcp6       0      0 :::465                  :::<span class="k">*</span>                    LISTEN</code></pre></figure>

<p>Efectivamente, el proceso está escuchando peticiones tal y como debería, de manera que todo está listo para enviar un correo desde un cliente externo.</p>

<h2 id="configuración-de-cliente-thunderbird">Configuración de cliente Thunderbird</h2>

<p>Una vez configurados los dos protocolos necesarios para recibir y enviar correos desde un cliente externo, pues hasta ahora hemos estado llevando a cabo ambas acciones directamente desde el servidor, vamos a proceder a la configuración de un cliente <strong>Thunderbird</strong>, que nos permitirá llevar a cabo dichas acciones cotidianas de una forma mucho más “amigable” que una utilidad de línea de comandos.</p>

<p>El primer paso, como es lógico, consistirá en instalar el paquete <strong>thunderbird</strong>, aunque podríamos hacer uso de otro cliente distinto como <strong>Evolution</strong>, que viene instalado por defecto. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@debian:~# apt <span class="nb">install </span>thunderbird</code></pre></figure>

<p>Tras ello, podremos ejecutar la aplicación y se nos abrirá una ventana emergente en la que podremos llevar a cabo la configuración inicial con nuestro servidor de correo. En mi caso, el resultado final sería el siguiente:</p>

<p><img src="https://i.ibb.co/YyQX8tF/thunderbird1.jpg" alt="thunderbird1" title="Thunderbird" /></p>

<p>Como se puede apreciar, hemos establecido una configuración básica como el nombre a mostrar en los correos enviados, la dirección de correo electrónico, la contraseña, el servidor, los puertos a utilizar para cada uno de los protocolos…</p>

<p>Una vez establecida la configuración correcta, pulsaremos en “<strong>Done</strong>” para así añadir la cuenta de correo electronico a nuestro cliente gráfico. En mi caso, he procedido a enviar desde mi correo personal un correo electrónico a <strong>debian</strong>, para así verificar que la recepción del mismo se lleva a cabo tal y como debería.</p>

<p>No considero necesario mostrar el proceso de envío, sino el resultado final producido por dicha recepción, que sería el siguiente:</p>

<p><img src="https://i.ibb.co/2M14SNS/thunderbird2.jpg" alt="thunderbird2" title="Thunderbird" /></p>

<p>Como era de esperar, el correo electrónico ha sido correctamente recibido en nuestro cliente <strong>Thunderbird</strong>, por lo que podemos concluir que el protocolo <em>IMAP</em> se encuentra funcionando tal y como debería a través del puerto <strong>993/TCP</strong>, de manera que se están sincronizando correctamente los mensajes de correo con el servidor.</p>

<p>Todavía nos falta comprobar que el protocolo <em>SMTP</em> también está funcionando correctamente, de manera que llevaremos ahora la prueba a la inversa, enviando desde nuestro cliente un correo electrónico a mi correo personal, de la siguiente forma:</p>

<p><img src="https://i.ibb.co/PFM7zJT/thunderbird3.jpg" alt="thunderbird3" title="Thunderbird" /></p>

<p>Tras pulsar el botón “<strong>Send</strong>” no se me ha mostrado ningún error por pantalla, de manera que puedo decir con casi total seguridad que el envío se ha producido correctamente. Sin embargo, hasta que no lo verifiquemos en mi correo personal, no tendremos la certeza de ello:</p>

<p><img src="https://i.ibb.co/zSKcksd/thunderbird4.jpg" alt="gmail5" title="Gmail" /></p>

<p>Efectivamente, el correo ha sido correctamente recibido en <em>Gmail</em>, por lo que podemos concluir que el protocolo <em>SMTP</em> se encuentra funcionando tal y como debería a través del puerto <strong>465/TCP</strong>, de manera que se están enviando correctamente los mensajes de correo a través del servidor.</p>

<h2 id="instalación-de-webmail">Instalación de webmail</h2>

<p>En un ámbito empresarial, quizás puede ser más común el hecho de utilizar una aplicación centralizada que permita gestionar el correo del equipo mediante una interfaz web, ya que así no es necesario ir configurando el cliente para todos y cada uno de los equipos existentes en la empresa.</p>

<p>Para alcanzar este propósito, he decidido hacer uso del servidor web <strong>nginx</strong> alojado en la máquina VPS que se encuentra actualmente escuchando peticiones en los puertos <strong>80</strong> (HTTP) y <strong>443</strong> (HTTPS), de manera que crearemos un VirtualHost accesible desde un determinado nombre de dominio y serviremos a través del mismo una aplicación web de correo (<em>webmail</em>), de nombre <strong>Roundcube</strong>.</p>

<p>El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS <strong>iesgn19.es</strong>, concretamente nombrando un nuevo servicio <strong>webmail</strong> mediante un registro <em>CNAME</em> al registro <em>A</em> que apunta a la dirección <strong>51.210.109.246</strong>, que será a través del cuál accederemos al VirtualHost en cuestión, tal y como podemos apreciar a continuación:</p>

<p><img src="https://i.ibb.co/56RMN06/webmail9.jpg" alt="dns5" title="Zona DNS" /></p>

<p>Como bien sabemos, si queremos hacer uso del protocolo HTTPS para dicho nombre de dominio, necesitaremos generar un nuevo certificado firmado por una autoridad certificadora de considerable reputación, de la misma forma que previamente lo hemos hecho para el nombre <strong>mail.iesgn19.es</strong>.</p>

<p>Cuando nuestro certificado haya sido correctamente generado, todo estará listo para configurar el nuevo VirtualHost dentro del directorio <strong>/etc/nginx/sites-available/</strong>, cuyo nombre he decidido que en este caso sea <strong>roundcube</strong>, de manera que ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/nginx/sites-available/roundcube</code></pre></figure>

<p>Dentro del mismo, tendremos que crear dos directivas <strong>server</strong>, una para el VirtualHost accesible en el puerto 80 HTTP, dentro del cuál únicamente asignaremos el ServerName correspondiente y una redirección permanente para así forzar HTTPS. En la otra, para el VirtualHost accesible en el puerto 443 HTTPS, tendremos que configurar las siguientes directivas:</p>

<ul>
  <li><strong>server_name</strong>: Indicaremos el nombre de dominio a través del cuál accederemos al servidor.</li>
  <li><strong>ssl</strong>: Activa el motor SSL, necesario para hacer uso de HTTPS, por lo que su valor debe ser <strong>on</strong>.</li>
  <li><strong>ssl_certificate</strong>: Indicamos la ruta del certificado del servidor firmado por la <em>CA</em>. En este caso, <strong>/etc/letsencrypt/live/webmail.iesgn19.es/fullchain.pem</strong>.</li>
  <li><strong>ssl_certificate_key</strong>: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, <strong>/etc/letsencrypt/live/webmail.iesgn19.es/privkey.pem</strong>.</li>
</ul>

<p>El resultado final sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">server <span class="o">{</span>
        listen 80<span class="p">;</span>
        listen <span class="o">[</span>::]:80<span class="p">;</span>

        server_name webmail.iesgn19.es<span class="p">;</span>

        <span class="k">return </span>301 https://<span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="o">}</span>

server <span class="o">{</span>
        listen 443 ssl http2<span class="p">;</span>
        listen <span class="o">[</span>::]:443 ssl http2<span class="p">;</span>

        ssl    on<span class="p">;</span>
        ssl_certificate    /etc/letsencrypt/live/webmail.iesgn19.es/fullchain.pem<span class="p">;</span>
        ssl_certificate_key    /etc/letsencrypt/live/webmail.iesgn19.es/privkey.pem<span class="p">;</span>

        server_name webmail.iesgn19.es<span class="p">;</span>

        root /srv/roundcube<span class="p">;</span>

        index index.php index.html index.htm index.nginx-debian.html<span class="p">;</span>

        location / <span class="o">{</span>
            try_files <span class="nv">$uri</span> <span class="nv">$uri</span>/ /index.php<span class="p">;</span>
        <span class="o">}</span>

        location ~ <span class="se">\.</span>php <span class="o">{</span>
            try_files <span class="nv">$uri</span> <span class="o">=</span>404<span class="p">;</span>
            fastcgi_pass unix:/run/php/php7.3-fpm.sock<span class="p">;</span>
            fastcgi_index index.php<span class="p">;</span>
            fastcgi_param SCRIPT_FILENAME <span class="nv">$document_root$fastcgi_script_name</span><span class="p">;</span>
            include fastcgi_params<span class="p">;</span>
        <span class="o">}</span>

        location ~ /.well-known/acme-challenge <span class="o">{</span>
            allow all<span class="p">;</span>
        <span class="o">}</span>

        location ~ ^/<span class="se">\(</span>README|INSTALL|LICENSE|CHANGELOG|UPGRADING<span class="o">)</span><span class="se">\$</span> <span class="o">{</span>
            deny all<span class="p">;</span>
        <span class="o">}</span>

        location ~ ^/<span class="se">\(</span>bin|SQL<span class="o">)</span>/ <span class="o">{</span>
            deny all<span class="p">;</span>
        <span class="o">}</span>

        location ~<span class="se">\*</span> <span class="se">\.\(</span>jpg|jpeg|gif|png|webp|svg|woff|woff2|ttf|css|js|ico|xml<span class="o">)</span><span class="se">\$</span> <span class="o">{</span>
            access_log        off<span class="p">;</span>
            log_not_found     off<span class="p">;</span>
            expires           360d<span class="p">;</span>
        <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Como se puede apreciar, el fichero de configuración final es bastante complejo, aunque para la tranquilidad de los lectores, no es para nada necesario comprender qué hacen las directivas contenidas para hacer uso de la aplicación web. Cuando hayamos finalizado, simplemente guardaremos los cambios en el fichero.</p>

<p>Si nos fijamos, el directorio en el que debemos alojar todos los ficheros necesarios para el correcto funcionamiento de la aplicación es <strong>/srv/roundcube</strong>, directorio que todavía no ha sido generado, de manera que procederemos a ello haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">mkdir</span> /srv/roundcube</code></pre></figure>

<p>El directorio que contendrá todos los ficheros ya ha sido generado, de manera que para trabajar de una forma mucho más cómoda, nos moveremos dentro del mismo ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">cd</span> /srv/roundcube/</code></pre></figure>

<p>En este caso, vamos a utilizar la última versión de <strong>Roundcube</strong> disponible (1.4.11), que podremos descargar desde el <a href="https://github.com/roundcube/roundcubemail/">repositorio oficial</a>, concretamente desde <a href="https://github.com/roundcube/roundcubemail/releases/download/1.4.11/roundcubemail-1.4.11-complete.tar.gz">aquí</a>. Para ello, haremos uso de <code class="language-plaintext highlighter-rouge">wget</code> para descargar el paquete comprimido de <strong>Roundcube</strong> desde dicha web:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# wget https://github.com/roundcube/roundcubemail/releases/download/1.4.11/roundcubemail-1.4.11-complete.tar.gz
<span class="nt">--2021-02-16</span> 09:17:35--  https://github.com/roundcube/roundcubemail/releases/download/1.4.11/roundcubemail-1.4.11-complete.tar.gz
Resolving github.com <span class="o">(</span>github.com<span class="o">)</span>... 140.82.121.4
Connecting to github.com <span class="se">\(</span>github.com<span class="o">)</span>|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://github-releases.githubusercontent.com/4224042/fe83ab00-6a4d-11eb-8ff3-e714480567a4?X-Amz-Algorithm<span class="o">=</span>AWS4-HMAC-SHA256&amp;X-Amz-Credential<span class="o">=</span>AKIAIWNJYAX4CSVEH53A%2F20210216%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date<span class="o">=</span>20210216T081557Z&amp;X-Amz-Expires<span class="o">=</span>300&amp;X-Amz-Signature<span class="o">=</span>08ea29d4650c0bea51417b17b9578fe657c5c6c87a05209f8991957d88123159&amp;X-Amz-SignedHeaders<span class="o">=</span>host&amp;actor_id<span class="o">=</span>0&amp;key_id<span class="o">=</span>0&amp;repo_id<span class="o">=</span>4224042&amp;response-content-disposition<span class="o">=</span>attachment%3B%20filename%3Droundcubemail-1.4.11-complete.tar.gz&amp;response-content-type<span class="o">=</span>application%2Foctet-stream <span class="o">[</span>following]
<span class="nt">--2021-02-16</span> 09:17:35--  https://github-releases.githubusercontent.com/4224042/fe83ab00-6a4d-11eb-8ff3-e714480567a4?X-Amz-Algorithm<span class="o">=</span>AWS4-HMAC-SHA256&amp;X-Amz-Credential<span class="o">=</span>AKIAIWNJYAX4CSVEH53A%2F20210216%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date<span class="o">=</span>20210216T081557Z&amp;X-Amz-Expires<span class="o">=</span>300&amp;X-Amz-Signature<span class="o">=</span>08ea29d4650c0bea51417b17b9578fe657c5c6c87a05209f8991957d88123159&amp;X-Amz-SignedHeaders<span class="o">=</span>host&amp;actor_id<span class="o">=</span>0&amp;key_id<span class="o">=</span>0&amp;repo_id<span class="o">=</span>4224042&amp;response-content-disposition<span class="o">=</span>attachment%3B%20filename%3Droundcubemail-1.4.11-complete.tar.gz&amp;response-content-type<span class="o">=</span>application%2Foctet-stream
Resolving github-releases.githubusercontent.com <span class="o">(</span>github-releases.githubusercontent.com<span class="o">)</span>... 185.199.108.154, 185.199.109.154, 185.199.110.154, ...
Connecting to github-releases.githubusercontent.com <span class="se">\(</span>github-releases.githubusercontent.com<span class="o">)</span>|185.199.108.154|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7048262 <span class="o">(</span>6.7M<span class="o">)</span> <span class="o">[</span>application/octet-stream]
Saving to: ‘roundcubemail-1.4.11-complete.tar.gz’

roundcubemail-1.4.11-complete.tar.gz                100%[<span class="o">==========================&gt;]</span>  6.72M  6.75MB/s    <span class="k">in </span>1.0s

2021-02-16 09:17:37 <span class="o">(</span>6.75 MB/s<span class="o">)</span> - ‘roundcubemail-1.4.11-complete.tar.gz’ saved <span class="o">[</span>7048262/7048262]</code></pre></figure>

<p>Para verificar que el comprimido se ha descargado correctamente, listaremos el contenido del directorio actual haciendo para ello uso del comando <code class="language-plaintext highlighter-rouge">ls -l</code>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">ls</span> <span class="nt">-l</span>
total 6884
<span class="nt">-rw-r--r--</span> 1 root root 7048262 Feb  8 20:41 roundcubemail-1.4.11-complete.tar.gz</code></pre></figure>

<p>Efectivamente, se ha descargado un paquete de nombre “<strong>roundcubemail-1.4.11-complete.tar.gz</strong>” con un peso total de <strong>6.72 MB</strong> (7048262 <em>bytes</em>).</p>

<p>Al estar comprimido el fichero, no podremos hacer uso de la aplicación hasta que no hagamos una extracción de los ficheros contenidos. Además, para liberar espacio, borraremos tras ello el fichero comprimido ya que no nos hará falta. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">tar</span> <span class="nt">-zxf</span> roundcubemail-1.4.11-complete.tar.gz <span class="nt">--strip</span> 1 <span class="o">&amp;&amp;</span> <span class="nb">rm </span>roundcubemail-1.4.11-complete.tar.gz</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>-z</strong>: Utiliza gzip para descomprimir el fichero.</li>
  <li><strong>-x</strong>: Indica a tar que desempaquete el fichero.</li>
  <li><strong>-f</strong>: Indica a tar que el siguiente argumento es el nombre del fichero .tar.gz.</li>
  <li><strong>-–strip 1</strong>: Saltamos el primer directorio, ya que dentro del comprimido hay un directorio padre que no necesitamos.</li>
</ul>

<p>Para verificar que el fichero se ha descomprimido correctamente, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">ls</span> <span class="nt">-l</span>
total 404
drwxr-xr-x  2 501    80   4096 Feb 16 09:18 bin
<span class="nt">-rw-r--r--</span>  1 501    80 186666 Feb  8 20:29 CHANGELOG
<span class="nt">-rw-r--r--</span>  1 501 staff    911 Feb  8 20:29 composer.json
<span class="nt">-rw-r--r--</span>  1 501    80    943 Feb  8 20:29 composer.json-dist
<span class="nt">-rw-r--r--</span>  1 501    80  89041 Feb  8 20:29 composer.lock
drwxr-xr-x  2 501    80   4096 Feb 16 09:18 config
<span class="nt">-rw-r--r--</span>  1 501    80  12843 Feb  8 20:29 index.php
<span class="nt">-rw-r--r--</span>  1 501    80  12864 Feb  8 20:29 INSTALL
drwxr-xr-x  3 501    80   4096 Feb 16 09:18 installer
<span class="nt">-rw-r--r--</span>  1 501    80  35147 Feb  8 20:29 LICENSE
drwxr-xr-x  2 501    80   4096 Feb 16 09:18 logs
drwxr-xr-x 35 501    80   4096 Feb 16 09:18 plugins
drwxr-xr-x  8 501    80   4096 Feb 16 09:18 program
drwxr-xr-x  3 501    80   4096 Feb 16 09:18 public_html
<span class="nt">-rw-r--r--</span>  1 501    80   3810 Feb  8 20:29 README.md
drwxr-xr-x  5 501    80   4096 Feb 16 09:18 skins
drwxr-xr-x  7 501    80   4096 Feb  8 20:29 SQL
drwxr-xr-x  2 501    80   4096 Feb 16 09:18 temp
<span class="nt">-rw-r--r--</span>  1 501    80   4148 Feb  8 20:29 UPGRADING
drwxr-xr-x  9 501    80   4096 Feb 16 09:18 vendor</code></pre></figure>

<p>Efectivamente, todo el contenido se ha descomprimido tal y como queríamos (en lugar de descomprimir un directorio de nombre <strong>roundcubemail-1.4.11</strong> del que posteriormente tendríamos que mover los ficheros contenidos al directorio actual).</p>

<p>Ya tenemos todos los ficheros necesarios para llevar a cabo la instalación de <em>Roundcube</em>, pero hay un pequeño detalle que todavía no hemos contemplado, pues el usuario y el grupo propietario correspondiente a dichos ficheros y directorios es <strong>root</strong>, por lo que procedemos a cambiar dicho propietario y grupo de forma recursiva a <strong>www-data</strong>, haciendo para ello uso del comando <code class="language-plaintext highlighter-rouge">chown -R</code>, pues de otro modo no podría escribir en dichos ficheros durante la instalación:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">chown</span> <span class="nt">-R</span> www-data:www-data /srv/roundcube/</code></pre></figure>

<p>Listo, toda la configuración necesaria ya ha sido realizada, así que únicamente queda habilitar dicho sitio. Para activar el sitio, a diferencia de <em>apache2</em> que contaba con una utilidad para ello, tendremos crear el enlace simbólico al fichero de configuración ubicado en <strong>/etc/nginx/sites-available/</strong> dentro de <strong>/etc/nginx/sites-enabled/</strong> de forma manual. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/roundcube /etc/nginx/sites-enabled/</code></pre></figure>

<p>Al parecer, el sitio ha sido correctamente habilitado, pero para activar la nueva configuración, tendremos que volver a cargar la configuración del servicio <em>nginx</em>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# systemctl reload nginx</code></pre></figure>

<p>Nos falta un único paso para proceder con la instalación, y es que todavía no hemos creado la base de datos que utilizará, de manera que para acceder al motor de <em>mysql</em>, tendremos que hacer uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# mysql <span class="nt">-u</span> root <span class="nt">-p</span>
Enter password:
Welcome to the MariaDB monitor.  Commands end with <span class="p">;</span> or <span class="se">\g</span><span class="nb">.</span>
Your MariaDB connection <span class="nb">id </span>is 116
Server version: 10.3.27-MariaDB-0+deb10u1 Debian 10

Copyright <span class="o">(</span>c<span class="o">)</span> 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type <span class="s1">'help;'</span> or <span class="s1">'\h'</span> <span class="k">for </span>help. Type <span class="s1">'\c'</span> to clear the current input statement.</code></pre></figure>

<p>Cuando nos encontremos haciendo uso del motor <em>mysql</em>, procederemos a la creación de dicha base de datos, con nombre <strong>bd_roundcube</strong>, por ejemplo:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">MariaDB <span class="o">[(</span>none<span class="o">)]&gt;</span> CREATE DATABASE bd_roundcube<span class="p">;</span>
Query OK, 1 row affected <span class="o">(</span>0.001 sec<span class="o">)</span></code></pre></figure>

<p>La base de datos que usará <em>Roundcube</em> ya se encuentra creada, pero de nada nos sirve tener una base de datos si no tenemos un usuario que pueda acceder a la misma. En este caso, vamos a crear un usuario de nombre “<strong>user_roundcube</strong>” que tenga permitido el acceso desde <strong>localhost</strong> (es decir, desde la máquina local) y cuya contraseña sea “<strong>pass_roundcube</strong>” (lógicamente, en un caso real se usarían credenciales más seguras). Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">MariaDB <span class="o">[(</span>none<span class="o">)]&gt;</span> CREATE USER <span class="s1">'user_roundcube'</span>@<span class="s1">'localhost'</span> IDENTIFIED BY <span class="s1">'pass_roundcube'</span><span class="p">;</span>
Query OK, 0 rows affected <span class="o">(</span>0.004 sec<span class="o">)</span></code></pre></figure>

<p>El usuario ya ha sido generado, pero todavía no tiene permisos sobre la base de datos que acabamos de crear, así que debemos otorgarle dichos privilegios. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">MariaDB <span class="o">[(</span>none<span class="o">)]&gt;</span> GRANT ALL PRIVILEGES ON bd_roundcube.<span class="k">*</span> TO <span class="s1">'user_roundcube'</span>@<span class="s1">'localhost'</span><span class="p">;</span>
Query OK, 0 rows affected <span class="o">(</span>0.001 sec<span class="o">)</span></code></pre></figure>

<p>Una vez realizadas todas las modificaciones oportunas, podremos salir de <em>mysql</em> haciendo uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">MariaDB <span class="o">[(</span>none<span class="o">)]&gt;</span> quit
Bye</code></pre></figure>

<p><strong>Nota</strong>: No es necesario hacer uso del comando <code class="language-plaintext highlighter-rouge">FLUSH PRIVILEGES;</code>, a diferencia de varios artículos que he estado leyendo en Internet, que usan dicho comando muy a menudo sin necesidad alguna. Dicho comando, lo que hace es recargar la caché de las tablas GRANT que se encuentran en memoria, recarga que se hace de forma automática al hacer uso de una sentencia <strong>GRANT</strong>, de manera que no es necesario hacerlo manualmente. Para aclararlo, dicho comando únicamente debe utilizarse tras modificar las tablas GRANT de manera indirecta, es decir, tras usar sentencias INSERT, UPDATE o DELETE.</p>

<p>Antes de proceder con la correspondiente prueba de funcionamiento, tendremos que instalar dos librerías necesarias para el correcto funcionamiento de la aplicación web, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>php-intl php-imagick</code></pre></figure>

<p>En un principio, todas las configuraciones necesarias han sido llevadas a cabo, así que es hora de realizar la correspondiente prueba de acceso. Para ello, abriremos un navegador web y trataremos de acceder a <a href="https://webmail.iesgn19.es/installer">https://webmail.iesgn19.es/installer</a> para proceder con la instalación del mismo.</p>

<p>En caso de tener dudas sobre el proceso llevado a cabo, se recomienda llevar a cabo una lectura del artículo <a href="https://www.alvarovf.com/servicios/vps/2020/11/08/instalacion-servidor-lemp.html">Instalación de un servidor LEMP</a> en el que se tratan con mayor profundidad los pasos llevados a cabo durante la instalación.</p>

<p>Dentro del instalador, como se puede suponer, tendremos que indicar el valor de algunas directivas necesarias para la instalación, como por ejemplo, la base de datos a utilizar por la aplicación web y las credenciales del usuario previamente generado:</p>

<p><img src="https://i.ibb.co/4p14QNB/webmail3.jpg" alt="roundcube1" title="Roundcube" /></p>

<p>Tras ello, llegamos a la parte de configuración de los protocolos para la sincronización y envío de correos. Es importante comprender llegado este punto que la conexión entre el cliente y el servidor se está llevando a cabo actualmente de forma cifrada, mediante el uso del protocolo <em>HTTPS</em> en el puerto <strong>443/TCP</strong>, ya que estamos haciendo uso de una aplicación web.</p>

<p>Gracias al uso de dicho protocolo, la conexión entre el cliente y el servidor se encuentra cifrada, y teniendo también en cuenta que las peticiones al servidor de correos las estamos llevando a cabo de forma indirecta, ya que nosotros hacemos la petición a la aplicación web (cifrada) y es dicha aplicación la que se comunica con el servidor de correos de forma <strong>local</strong>, no será necesario utilizar las alternativas cifradas de los protocolos <em>IMAP</em> y <em>SMTP</em>, ya que como hemos mencionado, el uso de dichos protocolos se ejecuta de forma local, de manera que la conexión no sale de la máquina servidora y no es necesario cifrarla, evitando por tanto un consumo de recursos innecesario.</p>

<p>En conclusión, para el protocolo <em>IMAP</em> haremos uso del puerto <strong>143/TCP</strong>, tal y como podemos apreciar:</p>

<p><img src="https://i.ibb.co/N6df7jq/webmail4.jpg" alt="roundcube2" title="Roundcube" /></p>

<p>Lo mismo ocurre con el protocolo <em>SMTP</em>, de manera que utilizaremos el puerto <strong>25/TCP</strong>, de la siguiente forma:</p>

<p><img src="https://i.ibb.co/HDHrfXt/webmail4-1.jpg" alt="roundcube3" title="Roundcube" /></p>

<p>Por último, tendremos que indicar el idioma por defecto para la página. En mi caso, he optado por elegir el inglés (<strong>en_US</strong>), aunque podría haber elegido cualquier otro formateado de la forma indicada en el <strong>RFC1766</strong>:</p>

<p><img src="https://i.ibb.co/tHL4ZRp/webmail4-2.jpg" alt="roundcube4" title="Roundcube" /></p>

<p>Tras ello, podremos continuar y en el último apartado de la instalación se nos mostrará lo siguiente:</p>

<p><img src="https://i.ibb.co/mR3rKHk/webmail5.jpg" alt="roundcube5" title="Roundcube" /></p>

<p>Como se puede apreciar, la base de datos no ha sido inicializada, en el sentido de que es necesario crear una serie de tablas dentro de la misma para que la aplicación web haga uso de las mismas, de manera que pulsaremos en “<strong>Initialize database</strong>”, obteniendo el siguiente resultado:</p>

<p><img src="https://i.ibb.co/6WRj2H7/webmail6.jpg" alt="roundcube6" title="Roundcube" /></p>

<p>Tras unos segundos, las tablas ya habrán sido generadas en la base de datos y todo estará listo para comenzar a utilizar la aplicación web, no sin antes eliminar el directorio <strong>installer/</strong> actualmente existente dentro del DocumentRoot, para así evitar posibles vulnerabilidades, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:/srv/roundcube# <span class="nb">rm</span> <span class="nt">-r</span> installer/</code></pre></figure>

<p>Una vez eliminado el directorio, todo estará listo para acceder, ahora sí, a la página principal de la aplicación web, accediendo por tanto a <a href="https://webmail.iesgn19.es">https://webmail.iesgn19.es</a>, pudiendo apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/LnTTfQk/webmail7.jpg" alt="roundcube7" title="Roundcube" /></p>

<p>Nos aparecerá un formulario para iniciar sesión, en el que debemos introducir el nombre de usuario generado en la máquina servidora junto a su contraseña, en este caso, <strong>debian</strong>:</p>

<p><img src="https://i.ibb.co/tMvygWf/webmail10.jpg" alt="roundcube8" title="Roundcube" /></p>

<p>Efectivamente, el correo electrónico ha sido correctamente sincronizado con nuestra aplicación <strong>Roundcube</strong>, por lo que podemos concluir que el protocolo <em>IMAP</em> se encuentra funcionando tal y como debería a través del puerto <strong>143/TCP</strong>, de manera que se están sincronizando correctamente los mensajes de correo con el servidor.</p>

<p>Sin embargo, hay un pequeño fallo de configuración, y es que cuando enviamos un correo al exterior, se hace con el dominio <strong>@localhost</strong> como remitente, de manera que tendremos que modificarlo accediendo para ello al apartado <strong>Settings</strong> en el menú de la izquierda, seguido de <strong>Identities</strong> y pulsando en nuestra cuenta en cuestión. Una vez ahí, podremos establecer correctamente nuestro correo electrónico:</p>

<p><img src="https://i.ibb.co/p2gd7xZ/roundcubex.png" alt="roundcube9" title="Roundcube" /></p>

<p>Todavía nos falta comprobar que el protocolo <em>SMTP</em> también está funcionando correctamente, de manera que llevaremos ahora la prueba a la inversa, enviando desde nuestro cliente web un correo electrónico a mi correo personal, de la siguiente forma:</p>

<p><img src="https://i.ibb.co/vwVX3bq/smtp1.png" alt="roundcube10" title="Roundcube" /></p>

<p>Tras pulsar el botón “<strong>Send</strong>” no se me ha mostrado ningún error por pantalla, de manera que puedo decir con casi total seguridad que el envío se ha producido correctamente. Sin embargo, hasta que no lo verifiquemos en mi correo personal, no tendremos la certeza de ello:</p>

<p><img src="https://i.ibb.co/d5DGWjv/smtp2.png" alt="gmail6" title="Gmail" /></p>

<p>Efectivamente, el correo ha sido correctamente recibido en <em>Gmail</em>, por lo que podemos concluir que el protocolo <em>SMTP</em> se encuentra funcionando tal y como debería a través del puerto <strong>25/TCP</strong>, de manera que se están enviando correctamente los mensajes de correo a través del servidor.</p>

<h2 id="test-final">Test final</h2>

<p>Por último, y a modo de comprobación, vamos a hacer uso de una <a href="https://www.mail-tester.com/">herramienta</a> para verificar que todo el trabajo llevado a cabo hasta ahora ha servido y los servidores de correo que reciban mensajes procedentes de nosotros, los tendrán en cuenta de una forma muy posiblemente favorable.</p>

<p>Su funcionamiento es bastante sencillo a la vez que completo, consistente en enviar un correo electrónico a la dirección que se nos facilita por pantalla, tal y como se puede apreciar a continuación:</p>

<p><img src="https://i.ibb.co/6rgPjyr/test1.png" alt="tester1" title="mail-tester" /></p>

<p>En mi caso, he procedido a enviar desde mi cliente <strong>Thunderbird</strong> un correo electrónico a la dirección mostrada por pantalla, para así obtener una calificación resultante de llevar a cabo una serie de pruebas sobre el correo enviado.</p>

<p>No considero necesario mostrar el proceso de envío, sino el resultado final producido por dicha recepción, que tras pulsar en “<strong>A continuación comprueba tu puntuación</strong>” sería el siguiente:</p>

<p><img src="https://i.ibb.co/Qksq0m0/test3.png" alt="tester2" title="mail-tester" /></p>

<p>Finalmente, la puntuación obtenida ha sido de <strong>10/10</strong>, por lo que podemos concluir que hemos realizado un buen trabajo y los servidores de correo van a tratar de forma favorable los mensajes procedentes del nuestro.</p>

<p>A pesar de ello, me ha indicado algunos aspectos que podría mejorar, como por ejemplo añadir un registro <em>DMARC</em>, así como un encabezado de anulación de suscripción a la lista, principalmente pensado para aquellas personas que envíen correos de forma masiva a través de <em>newsletters</em>, por lo que me voy con un muy buen sabor de boca.</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="servicios" /><category term="vps" /><summary type="html"><![CDATA[Este es el cuarto artículo referente a la configuración del VPS, una máquina virtual contratada en OVH que cuenta con un direccionamiento público 51.210.109.246. Además, se ha contratado una zona DNS en la que nombraremos dicha máquina junto a los servicios que despleguemos, en el dominio iesgn19.es. La intención es la de ir desarrollando a lo largo del curso una serie de posts en los que se detallen las configuraciones llevadas a cabo en dicho VPS, así como el mantenimiento de las mismas. En el día de hoy, vamos a instalar un servidor de correo Postfix, así como llevar a cabo determinadas configuraciones para ampliar las funcionalidades del mismo. Antes de comenzar con la instalación de Postfix, es recomendable conocer de una forma más o menos superficial el método de funcionamiento del correo electrónico. Como todos bien sabemos, para enviar un correo electrónico necesitamos conocer la dirección de correo de un usuario, que tendrá la siguiente forma: usuario@nombre_servicio De forma general, el nombre del servicio de correo es el nombre del dominio de la organización a la que pertenece el usuario. Por ejemplo, alvarovf.com. Cada vez que enviamos un correo a esa dirección, tenemos que determinar de alguna forma la dirección IP del servidor de correo existente en la organización, siguiendo para ello los siguientes pasos: Si el nombre detrás de la @ está asociado a un registro A en un servidor DNS, ya conocemos la dirección del servidor de correo. En caso contrario, se consultaría en el servidor DNS por el registro MX que indique el nombre del servidor de correo asociado al nombre de dominio. Sin entrar en demasiado detalle, existen determinados conceptos que debemos conocer respecto a los servidores de correo y la infraestructura existente a su alrededor: MUA (Mail User Agent): Programa que permite a un usuario, como mínimo, leer y escribir mensajes de correo electrónico. Generalmente conocido como cliente de correo. Por ejemplo, Evolution, Outlook, Thunderbird… MTA (Mail Transfer Agent): Programa que transfiere los mensajes de correo electrónico entre máquinas que usan el protocolo SMTP. Un mensaje puede pasar por varios MTA hasta llegar al destino final. Generalmente conocido como servidor de correo. Por ejemplo, Postfix, qmail, exim… MDA (Mail Delivery Agent): Programas utilizados por los agentes MTA para entregar el correo electrónico al buzón de un usuario concreto. Esta entrega se puede hacer localmente en el servidor o de forma remota utilizando el protocolo POP3 o IMAP. SMTP (Simple Mail Transfer Protocol): Protocolo de red utilizado para el intercambio de mensajes de correo electrónico. Existe una mejora del protocolo llamada ESMTP (Enhanced Simple Mail Transfer Protocol). POP3 (Post Office Protocol): Protocolo para recuperar correos electrónicos de un MDA. Su principal característica es que se descargan todos los correos. IMAP (Internet Message Access Protocol): Protocolo para recuperar correos electrónicos de un MDA. A diferencia del anterior, se sincroniza el estado de los correos entre el servidor y el cliente. Por último, de una manera un tanto superficial, vamos a comprender el proceso que se sigue cada vez que un correo electrónico es enviado de un cliente a otro: 1. Un usuario utiliza un MUA para enviar el correo electrónico a su servidor de correos (MTA). Este envío se hace usando el protocolo SMTP. El nombre del servidor tendrá que estar definido en un servidor DNS, y en un principio usaremos el puerto 25/TCP, estableciendo una conexión no cifrada ni autentificada. Por ese mismo motivo, es más común utilizar el protocolo ESMTP, que utiliza el puerto 587/TCP y permite la autentificación y el cifrado de la comunicación. 2. El MTA recibe el correo desde el MUA: Si la dirección del destinatario del correo es la misma que la que controla el servidor receptor, el correo no se envía a ningún MTA y se le da al MDA para que lo guarde en el buzón del usuario destinatario. Si la dirección del destinatario del correo es distinta que la que controla el servidor receptor, el correo se envía al MTA correspondiente a la dirección del destinatario, pudiéndose hacer de dos formas: Si por cualquier razón el MTA no puede enviar el correo directamente al MTA destino, se tendrá configurado un servidor MTA intermediario (relay) que será el responsable de enviarlo al destinatario final. Si el MTA no tiene configurado un servidor relay intermediario, tendrá que averiguar la dirección IP del servidor correspondiente al nombre de correo del destinatario, normalmente haciendo una consulta MX al servidor DNS. En caso de que existan varios servidores de correo definidos, le intentará mandar el correo al más prioritario (el que tiene el número más pequeño). 3. Cuando el correo llega al MTA destino, se pasa el correo al MDA que lo guardará en el buzón del usuario destinatario. 4. El usuario destinatario utilizará una MUA para conectarse al servidor MDA y recuperar el correo: Puede utilizar el protocolo POP3, por lo que se conectará al servidor POP3. Esta conexión está autentificada y puede estar cifrada. El protocolo POP3 suele hacer uso del puerto 110/TCP. Con este protocolo lo que hacemos es descargar todos los correos desde nuestro buzón remoto a nuestro MUA. Puede usar el protocolo IMAP, por lo que se conectará al servidor IMAP. Esta conexión está autentificada y puede estar cifrada. El protocolo IMAP suele hacer uso del puerto 143/TCP. Con este protocolo lo que hacemos es sincronizar el estado de los correos desde nuestro buzón remoto a nuestro MUA, de tal manera que podemos acceder desde distintos MUA y obtendremos el mismo estado de los correos en todos ellos. Es imprescindible si vamos a usar un MUA que sea una aplicación web. Una vez conocido el método de funcionamiento del correo electrónico y algunos conceptos necesarios, todo estará listo para instalar un MTA de nombre Postfix, que nos permitirá transferir mensajes de correo electrónico entre máquinas que utilicen el protocolo SMTP. Sin embargo, antes de ello es necesario realizar algunas modificaciones en nuestra zona DNS, pues para poder enviar y recibir correos desde nuestro VPS necesitamos cumplir los siguientes requisitos: En este caso, no es necesario, ya que el VPS es capaz de enviar el correo electrónico al exterior por sí mismo, pero en otras situaciones, sería necesario configurar un servidor de correo intermediario (relay). Necesitamos configurar en nuestra zona DNS un registro MX apuntando a nuestra máquina, necesario para la recepción de correos, pues así el resto de máquinas pueden conocer la IP de nuestro servidor de correo. Necesitamos implementar varias técnicas (al menos un registro SPF del que posteriormente hablaremos) para asegurar que el servidor de correo al que mandamos nuestro correo confíe en nosotros, para así evitar suplantaciones. Necesitamos que nuestra IP pública esté “limpia”. Generalmente, las direcciones IP dinámicas suelen estar incluidas en una lista negra que comprobarán los servicios como Gmail, Hotmail o Yahoo para evitar el spam, ya que previamente han sido utilizadas con dicha finalidad. Podemos comprobar si nuestra dirección IP se encuentra en una lista negra haciendo uso de esta utilidad. El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS iesgn19.es, concretamente nombrando un nuevo servicio mail mediante un registro A que apunta a la dirección 51.210.109.246, que será el nombre del servicio al que posteriormente apuntará el registro MX, tal y como podemos apreciar a continuación: Muy posiblemente estéis pensando en el motivo por el que he creado un registro A apuntando a la dirección IP de la VPS en lugar de crear un registro CNAME que apunte a un registro A ya existente. En realidad, podríamos haber creado el registro CNAME que acabamos de mencionar apuntando a un registro A ya existente y funcionaría a la perfección, sin embargo, no es considerado una buena práctica, pues idealmente, los registros que apunten a servidores de correo deben ser registros A. Desconozco el motivo real, pues lo único que se me ocurre es que la finalidad sea acelerar la petición, ya que se haría una única petición (registro A) en lugar de dos (registro CNAME). Tras ello, todo estará listo para crear el nuevo registro MX que indicará el servidor al que los servidores de correo externos deben enviar los correos cuyo dominio destinatario sea iesgn19.es. Le asignaremos la prioridad máxima (1), aunque en este caso es irrelevante, pues únicamente tenemos un servidor, quedando de la siguiente forma: Por último, tendremos que implementar al menos una técnica para asegurar que el servidor de correo al que mandamos nuestro correo confíe en nosotros, para así evitar suplantaciones. En este caso, vamos a configurar un registro SPF, no sin antes explicar en qué consiste. En principio, cualquier máquina puede enviar mensajes de correo a cualquier destino utilizando cualquier remitente, pero esta característica del correo ha sido masivamente utilizada para inundar de mensajes no deseados a los servidores de correo y hacer suplantaciones del remitente (email spoofing), por lo que se ha generalizado la implementación de medidas complementarias para reducir al máximo este problema y hoy en día, la mayoría de servidores de correo o rechazan o mandan a la bandeja de spam los mensajes que lleguen desde servidores que no implementen algún mecanismo adicional de autenticación. Como ya hemos mencionado, en este caso vamos a utilizar SPF (Sender Policy Framework), un mecanismo de autenticación que describe de forma explícita mediante un registro DNS de tipo TXT las direcciones IP y nombres DNS autorizados a enviar correo desde un determinado dominio. Se pueden especificar diversos campos en dicho registro, pero en este caso, que tenemos un solo equipo con una dirección IPv4, pondremos un registro SPF parecido al siguiente: IN TXT "v=spf1 mx ~all" Dentro del mismo, podemos hacer referencia a nuestro servidor de correo de diferentes formas: a: La IP de un registro A de un nombre del DNS. mx: Registro MX del DNS del dominio. ptr: Registro PTR del servidor de correo. ip4: Direcciones IPv4. ip6: Direcciones IPv6. En este caso, como la dirección IP a la que apunta el registro MX coincide con la máquina que va a enviar el correo al exterior, he referenciado al servidor de correo mediante mx, aunque también podría haber puesto ip4:51.210.109.246/32. Es importante mencionar la importancia del signo que aparece antes de all, ya que podemos indicarle a los servidores destinatarios lo que deben hacer si reciben correo desde otra máquina diferente a las referenciadas en el registro anterior: -: Descartar el mensaje. ~: Clasificarlo como spam. ?: Aceptar el mensaje. En este caso, no vamos a pillarnos los dedos y vamos a indicar que los correos electrónicos recibidos desde una máquina distinta a la VPS se clasifique como spam, pero evitando que se descarte, para así comprobar que el envío se está realizando correctamente. De esta forma, el correo que enviemos desde la máquina VPS pasará los filtros SPF en el destino y la mayoría de nuestros correos llegarán al destino con poca probabilidad de que se clasifiquen como spam. El registro tendrá finalmente la siguiente forma: Una vez finalizada la configuración en la zona DNS de nuestro dominio, todo estará listo para proceder con la instalación y configuración inicial del servidor de correos Postfix. Instalación de Postfix Además de llevar a cabo la instalación del paquete postfix, instalaremos a su vez el paquete bsd-mailx, que nos proporcionará las utilidades necesarias para llevar a cabo el envío de correo (mail), no sin antes actualizar la lista de la paquetería disponible, así como asegurar que toda la paquetería instalada en la máquina se encuentra en su última versión, por lo que ejecutaremos el comando: root@vps:~# apt update &amp;&amp; apt upgrade &amp;&amp; apt install postfix bsd-mailx Durante la instalación, nos aparecerá una ventana en la que debemos seleccionar una opción para que el paquete se configure en base a nuestras necesidades. En nuestro caso, dado que la máquina es capaz de enviar y recibir correo electrónico por sí misma a través del protocolo SMTP, seleccionaremos la opción Internet Site, de la siguiente manera: En el siguiente paso de la instalación se nos pedirá el nombre del sistema de correo, es decir, el nombre que se utilizará para cualificar los correos electrónicos en cuestión, o en otras palabras, lo que irá indicado después de @. Por ejemplo, para el correo debian@iesgn19.es, el valor correcto sería iesgn19.es, quedando de la siguiente forma: El paquete postfix ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto un socket TCP/IP en el puerto por defecto del protocolo SMTP (25) que estará escuchando peticiones en todas las interfaces de la máquina (0.0.0.0), así que para verificarlo haremos uso del comando: root@vps:~# netstat -tln Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::25585 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 :::25 :::* LISTEN tcp6 0 0 :::443 :::* LISTEN Donde: -t: Filtramos únicamente para las conexiones que utilizan el protocolo TCP. -l: Filtramos únicamente para los sockets que están actualmente escuchando peticiones (State = LISTEN). -n: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos. Efectivamente, el proceso está escuchando peticiones tal y como debería. A pesar de estar escuchando peticiones en todas las interfaces de la máquina, eso no supone que cualquier persona ajena a nosotros pueda utilizar nuestro servidor para enviar correos, ya que para ello existe indicada una directiva mynetworks en el fichero /etc/postfix/main.cf que se encuentra limitada al direccionamiento 127.0.0.0/8, lo que supone que nadie podrá hacer relay en nuestro servidor. Sin embargo, dado que el protocolo SMTP también utiliza el puerto 25/TCP para la recepción de correos (además de para hacer relay), será necesario tenerlo abierto para todo el mundo, pues de lo contrario, imposibilitaríamos la recepción de los mismos. Envío de correos desde el servidor Una vez llevada a cabo toda la configuración necesaria, todo estará listo para hacer uso de la utilidad mail para enviar correos al exterior. En este caso, vamos a enviar un correo de prueba a mi correo personal, ejecutando para ello el comando: debian@vps:~$ mail avacaferreras@gmail.com Subject: Correo de prueba. Buenos días, esto es un correo de prueba. Cc: En este caso, hemos redactado un correo desde el usuario debian al correo destino avacaferreras@gmail.com, cuyo asunto será “Correo de prueba.” y su contenido, “Buenos días, esto es un correo de prueba.”. Considero necesario mencionar que en caso de hacer uso de la utilidad de línea de comandos para enviar correos, finalizaremos la escritura del mismo con la combinación de teclas CTRL + D. En un principio, el envío del correo electrónico se ha producido correctamente, sin embargo, vamos a proceder a verificarlo visualizando para ello los logs producidos por dicho envío, en el fichero /var/log/mail.log, haciendo para ello uso del comando: debian@vps:~$ cat /var/log/mail.log Jan 21 08:55:12 vps postfix/pickup[15685]: 1CD67E13D1: uid=1000 from=&lt;debian&gt; Jan 21 08:55:12 vps postfix/cleanup[15693]: 1CD67E13D1: message-id=&lt;20210121075512.1CD67E13D1@vps.iesgn19.es&gt; Jan 21 08:55:12 vps postfix/qmgr[4212]: 1CD67E13D1: from=&lt;debian@iesgn19.es&gt;, size=445, nrcpt=1 (queue active) Jan 21 08:55:12 vps postfix/smtp[15695]: connect to gmail-smtp-in.l.google.com[2a00:1450:400c:c01::1b]:25: Network is unreachable Jan 21 08:55:12 vps postfix/smtp[15695]: 1CD67E13D1: to=&lt;avacaferreras@gmail.com&gt;, relay=gmail-smtp-in.l.google.com[172.253.120.26]:25, delay=0.59, delays=0.01/0/0.44/0.15, dsn=2.0.0, status=sent (250 2.0.0 OK 1611215712 y9si3791494wrs.536 - gsmtp) Jan 21 08:55:12 vps postfix/qmgr[4212]: 1CD67E13D1: removed Como se puede apreciar en la penúltima línea mostrada, el estado del correo enviado desde debian@iesgn19.es a avacaferreras@gmail.com es sent (250), por lo que podemos asegurar que el envío se ha producido correctamente desde nuestro extremo. Todavía queda verificar que la recepción del mismo también se ha llevado a cabo tal y como debería, de manera que accederé a mi correo personal y comprobaré si ha aparecido en la bandeja de entrada: Efectivamente, la recepción del correo electrónico ha sido correcta, gracias a haber indicado en nuestra zona DNS un registro SPF para que Gmail pueda verificar la procedencia de dicho correo, y por tanto, pueda confiar en nosotros. De igual forma, podremos ver el contenido original de dicho correo electrónico junto a sus cabeceras pulsando para ello en los tres puntos ubicados en la parte derecha del mismo, seleccionando tras ello la opción “Mostrar original”, que tal y como se puede apreciar, en este caso tiene la siguiente estructura: Si nos fijamos con detenimiento, la directiva Received-SPF tiene el valor pass, lo que significa que la validación del remitente ha sido efectiva gracias al registro SPF que previamente hemos definido, en el que hemos autorizado a la dirección IP 51.210.109.246 a enviar correos electrónicos desde el dominio iesgn19.es. Recepción de correos desde el servidor Una vez que hemos comprobado que el envío de correos desde nuestra máquina al exterior funciona como debería, vamos a realizar el procedimiento inverso, en el que vamos a recibir correos en nuestra máquina que hayan sido enviados desde máquinas ajenas. Para ello, vamos a responder al mensaje en Gmail, que en este caso irá destinado al usuario debian existente en el dominio iesgn19.es, cuyo contenido será “Prueba de respuesta.”, de manera que el resultado final sería el siguiente: En un principio, el envío y la recepción del correo electrónico se ha producido correctamente, sin embargo, vamos a proceder a verificarlo visualizando para ello los logs producidos por la correspondiente recepción, en el fichero /var/log/mail.log, haciendo para ello uso del comando: debian@vps:~$ cat /var/log/mail.log Jan 21 08:56:58 vps postfix/smtpd[15713]: connect from mail-wm1-f42.google.com[209.85.128.42] Jan 21 08:56:58 vps postfix/smtpd[15713]: AA1CDE13D0: client=mail-wm1-f42.google.com[209.85.128.42] Jan 21 08:56:58 vps postfix/cleanup[15720]: AA1CDE13D0: message-id=&lt;CANR0p-363sCZ-+s7CsHmpnD-Cr-CetvtTFwWZS_MUkZpJFzZUA@mail.gmail.com&gt; Jan 21 08:56:58 vps postfix/qmgr[4212]: AA1CDE13D0: from=&lt;avacaferreras@gmail.com&gt;, size=3381, nrcpt=1 (queue active) Jan 21 08:56:58 vps postfix/local[15721]: AA1CDE13D0: to=&lt;debian@iesgn19.es&gt;, relay=local, delay=0.02, delays=0.01/0.01/0/0, dsn=2.0.0, status=sent (delivered to mailbox) Jan 21 08:56:58 vps postfix/qmgr[4212]: AA1CDE13D0: removed Jan 21 08:56:58 vps postfix/smtpd[15713]: disconnect from mail-wm1-f42.google.com[209.85.128.42] ehlo=2 starttls=1 mail=1 rcpt=1 bdat=1 quit=1 commands=7 Como se puede apreciar en la antepenúltima línea mostrada, el estado del correo enviado desde avacaferreras@gmail.com a debian@iesgn19.es es sent (250), por lo que podemos asegurar que la recepción se ha producido correctamente desde nuestro extremo. Tras ello, volveré a hacer uso de la utilidad mail para acceder a la bandeja de entrada (buzón) del usuario debian: debian@vps:~$ mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/debian": 1 message 1 new &gt;N 1 avacaferreras@gma Thu Jan 21 08:56 70/3476 Re: Correo de prueba. Efectivamente, la recepción del correo electrónico ha sido correcta, gracias a haber indicado en nuestra zona DNS un registro MX para que Gmail pueda encontrar la dirección IP de la máquina a la que debe enviar los correos para el dominio iesgn19.es. De igual forma, podremos ver el contenido de dicho correo electrónico junto a sus cabeceras eligiendo numéricamente el correo que queremos visualizar (en este caso, 1), pudiendo apreciar lo siguiente: Message 1: From avacaferreras@gmail.com Thu Jan 21 08:56:58 2021 X-Original-To: debian@iesgn19.es ... From: =?UTF-8?Q?=C3=81lvaro_Vaca_Ferreras?= &lt;avacaferreras@gmail.com&gt; Date: Thu, 21 Jan 2021 08:56:47 +0100 Subject: Re: Correo de prueba. To: Debian &lt;debian@iesgn19.es&gt; Content-Type: multipart/alternative; boundary="0000000000007f234505b9646a06" --0000000000007f234505b9646a06 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Prueba de respuesta. ... Como se puede apreciar, el contenido al completo de dicho correo electrónico ha sido correctamente visualizado desde nuestra utilidad de línea de comandos, por lo que podemos verificar que la recepción de correos está totalmente operativa. Alias y redirecciones Una característica muy interesante es que podemos permitir a los procesos que se encuentren en ejecución en la máquina enviar correos para así informar sobre su estado, por ejemplo, podemos crear una tarea cron en la que el resultado de la ejecución de dicho comando se notifique mediante un correo a un usuario concreto. En este caso, vamos a crear una nueva tarea que nos envíe cada minuto la hora del sistema (resultado de la ejecución del comando date), ejecutando para ello el comando: root@vps:~# crontab -e Donde: -e: Permite realizar modificaciones en el crontab actual y cargar de forma automática las modificaciones llevadas a cabo. En este caso, tendremos que indicar como mínimo dos nuevas directivas, siendo la primera de ellas aquella en la que indicaremos el usuario al que queremos enviar dichos correos informando del resultado de las ejecuciones (generalmente suele ser root) y la segunda, el comando en cuestión, precedido de la frecuencia de ejecución. Explicar cómo se define la frecuencia de ejecución de cualquier comando indicado en el crontab se saldría del objetivo de este artículo, de manera que aquí se podrá encontrar una página del manual en la que se explica de forma detallada y con ejemplos que ayudan a su comprensión. El resultado final sería: MAILTO = root * * * * * date El hecho de haber indicado 5 * supone la ejecución de dicho comando 1 vez por minuto, por lo que a lo largo de una hora recibiremos 60 correos informando sobre la ejecución de dicho comando. Es importante mencionar que la directiva MAILTO afecta a todos los comandos indicados tras la misma, de manera que el resultado de aquellos que se hayan indicado previamente no será notificado por correo. Tras esperar un minuto para así dar lugar a la ejecución de dicho comando, volveré a hacer uso de la utilidad mail para acceder a la bandeja de entrada (buzón) del usuario root: root@vps:~# mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/root": 1 message 1 new &gt;N 1 root@iesgn19.es Thu Jan 21 09:32 22/683 Cron &lt;root@vps&gt; date Efectivamente, la recepción local del correo electrónico ha sido correcta, así que procederemos a visualizar el contenido de dicho correo electrónico junto a sus cabeceras eligiendo numéricamente al igual que en el caso anterior, pudiendo apreciar lo siguiente: Message 1: From root@iesgn19.es Thu Jan 21 09:32:01 2021 X-Original-To: root From: root@iesgn19.es (Cron Daemon) To: root@iesgn19.es Subject: Cron &lt;root@vps&gt; date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Cron-Env: &lt;MAILTO=root&gt; X-Cron-Env: &lt;SHELL=/bin/sh&gt; X-Cron-Env: &lt;HOME=/root&gt; X-Cron-Env: &lt;PATH=/usr/bin:/bin&gt; X-Cron-Env: &lt;LOGNAME=root&gt; Date: Thu, 21 Jan 2021 09:32:01 +0100 (CET) Thu 21 Jan 2021 09:32:01 AM CET Como se puede apreciar, el contenido al completo de dicho correo electrónico ha sido correctamente visualizado desde nuestra utilidad de línea de comandos, por lo que podemos verificar que la recepción de correos para aquellas tareas cron configuradas se encuentra totalmente operativa. En este caso estamos llevando a cabo un ejemplo que carece de sentido, pues mi intención es únicamente mostrar el funcionamiento, pero en situaciones reales, puede llegar a ser bastante útil, por ejemplo, para avisar al administrador del estado de la copia de seguridad diaria. Sin embargo, todavía podemos ir un paso más allá y hacer uso de los alias, que nos permitirán redirigir el correo que un determinado usuario reciba al buzón de otro usuario ubicado en la misma máquina. Por ejemplo, podríamos redirigir el correo de todos los usuarios de la máquina al buzón de root y posteriormente, redirigir una vez más los correos entrantes para root a los usuarios administradores del sistema, para así gestionarlo de forma centralizada. Para esta ocasión, he decidido redirigir todos los correos entrantes al usuario root al buzón del usuario debian, consiguiendo por tanto que el correo con el resultado de la ejecución de la tarea cron acabe finalmente en el buzón de debian. Para ello, tendremos que modificar el fichero /etc/aliases, haciendo para ello uso del comando: root@vps:~# nano /etc/aliases El contenido por defecto de dicho fichero es el siguiente: postmaster: root Dentro del mismo, tendremos que añadir una línea por cada alias que deseemos crear, en la que indicaremos al principio el usuario del que queremos redirigir los correos y posteriormente, el usuario que actuará como destinatario final. En este caso, el resultado final sería: postmaster: root root: debian Dado que hemos modificado un fichero de configuración, tendremos que ejecutar el comando newaliases para que los cambios surtan efecto, de la siguiente forma: root@vps:~# newaliases El nuevo alias ya habrá sido generado y se encuentra actualmente en vigor, de manera que el resultado de la próxima ejecución de la tarea cron acabará finalmente en el buzón de debian, tal y como se puede apreciar: debian@vps:~$ mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/debian": 1 message 1 new &gt;N 1 root@iesgn19.es Thu Jan 21 09:36 22/683 Cron &lt;root@vps&gt; date Una vez más, la recepción local del correo electrónico ha sido correcta, no siendo necesario visualizar de nuevo el contenido de dicho correo. Como hemos podido apreciar, los alias funcionan perfectamente entre usuarios existentes en la misma máquina, ¿pero qué ocurriría si quisiésemos reenviar dichos a un correo personal como puede ser Gmail? Para ello, tendríamos que acudir a las redirecciones, que nos permitirán enviar el correo que llegue a un usuario a una cuenta de correo exterior. Para esta ocasión, he decidido redirigir todos los correos entrantes al usuario debian a mi correo electrónico personal, consiguiendo por tanto que el correo con el resultado de la ejecución de la tarea cron acabe finalmente en Gmail. Para ello, tendremos que añadir una nueva línea (o tantas como deseemos) al fichero ~/.forward, ejecutando para ello el comando: debian@vps:~$ echo "avacaferreras@gmail.com" &gt;&gt; ~/.forward La nueva redirección ya habrá sido generada y se encuentra actualmente en vigor, de manera que el resultado de la próxima ejecución de la tarea cron acabará finalmente en mi correo electrónico personal, tal y como se puede apreciar: Tal y como hemos definido, la redirección a mi correo electrónico personal se ha llevado a cabo correctamente y he recibido en el mismo el resultado de la ejecución de la tarea, que como previamente he mencionado, puede llegar a ser algo muy útil en determinadas situaciones. Por último, antes de finalizar con los alias y redirecciones es importante revertir todos los cambios realizados, para así retomar el comportamiento normal y deseado. Configuración de DKIM Previamente hemos introducido el concepto del SPF, un mecanismo de autenticación para que los servidores de correo destinatarios puedan verificar nuestra identidad. Como se puede suponer, existen más mecanismos, como por ejemplo DKIM o DMARC. En este caso vamos a implementar además DKIM (DomainKeys Identified Mail), un método de autenticación pensado principalmente para corroborar la procedencia del correo y asegurar que el mensaje no ha sido modificado durante la transferencia del mismo, consistente en publicar mediante un registro TXT en el servidor DNS la clave pública del servidor de correos. Posteriormente, se firmarán con la correspondiente clave privada todos los mensajes emitidos, de manera que el receptor podrá verificar cada correo emitido utilizando la clave pública. Para configurar DKIM en nuestro servidor necesitaremos instalar los paquetes opendkim y opendkim-tools, haciendo para ello uso del comando: root@vps:~# apt install opendkim opendkim-tools Una vez instalados los paquetes necesarios, tendremos que llevar a cabo una serie de modificaciones en el fichero de configuración ubicado en /etc/opendkim.conf, el cuál editaremos ejecutando para ello el comando: root@vps:~# nano /etc/opendkim.conf El contenido por defecto de dicho fichero es el siguiente: Syslog yes UMask 007 PidFile /var/run/opendkim/opendkim.pid OversignHeaders From TrustAnchorFile /usr/share/dns/root.key UserID opendkim Socket local:/var/spool/postfix/opendkim/opendkim.sock Dentro del mismo, tendremos que realizar las siguientes modificaciones: Socket: Modificaremos el socket UNIX actualmente configurado por un socket TCP/IP en el puerto 8892 en localhost, para evitar problemas de conexión entre los servicios. Domain: Indicaremos el dominio para el que queremos configurar el mecanismo DKIM. En este caso, iesgn19.es. KeyFile: Indicamos la ruta en la que se alojará la clave privada que posteriormente generaremos. El nombre de la misma estará compuesto por [selector].private. En este caso, /etc/opendkim/keys/iesgn19.es/pruebadkim.private. Selector: Indicamos un nombre único que posteriormente debemos utilizar a la hora de subir la clave pública al servidor DNS, para que el destinatario pueda identificarla fácilmente. En este caso, pruebadkim. El resultado final sería: Syslog yes UMask 007 PidFile /var/run/opendkim/opendkim.pid OversignHeaders From TrustAnchorFile /usr/share/dns/root.key UserID opendkim Socket inet:8892@localhost Domain iesgn19.es KeyFile /etc/opendkim/keys/iesgn19.es/pruebadkim.private Selector pruebadkim Una vez realizadas las modificaciones oportunas, tendremos que modificar también el fichero /etc/default/opendkim para indicar de nuevo el socket TCP/IP que se utilizará para comunicar los servicios Postfix y opendkim, haciendo para ello uso del comando: root@vps:~# nano /etc/default/opendkim Dentro del mismo, tendremos que referenciar al socket TCP/IP ubicado en el puerto 8892 de la máquina local (localhost), quedando de la siguiente forma: SOCKET=inet:8892@localhost Tras ello, guardaremos los cambios y saldremos, para así proceder a modificar finalmente el fichero referente a Postfix, para que así utilice dicho mecanismo para firmar los correos salientes. Para ello, ejecutaremos el comando: root@vps:~# nano /etc/postfix/main.cf En su interior tendremos que introducir las líneas necesarias para indicarle entre otras cosas, el socket TCP/IP que ha de utilizar para el firmado de dichos correos, siendo las mismas las siguientes: milter_default_action = accept milter_protocol = 2 smtpd_milters = inet:localhost:8892 non_smtpd_milters = $smtpd_milters Todos los ficheros de configuración necesarios han sido correctamente modificados, de manera que únicamente nos faltaría generar dicho par de claves, siendo posteriormente utilizada la clave privada para firmar los correos y la clave pública (que tendremos que exponer en un registro TXT del DNS) para comprobar dicha firma. Como previamente hemos definido, el par de claves deberá encontrarse dentro de /etc/opendkim/keys/, en un subdirectorio con el nombre del dominio, en este caso, iesgn19.es/, así que procederemos a la creación de dicho subdirectorio haciendo para ello uso del comando: root@vps:~# mkdir -p /etc/opendkim/keys/iesgn19.es Donde: -p: Indica que se cree el directorio padre en caso de no existir. El nuevo directorio ya habrá sido generado, sin embargo, dado que el contenido que se va a ubicar en el mismo es de carácter sensible, tendremos que protegerlo adecuadamente para así evitar su lectura por personas no deseadas, cambiando para ello los permisos a 700 y el usuario y grupo propietario a opendkim, ejecutando para ello los comandos: root@vps:~# chmod 700 /etc/opendkim/keys/iesgn19.es/ root@vps:~# chown opendkim:opendkim /etc/opendkim/keys/iesgn19.es/ Tras ello, y con la finalidad de trabajar de una forma más comoda, nos moveremos dentro de dicho directorio haciendo uso del comando cd, para así proceder con la creación del par de claves. La creación es bastante sencilla, pues para ello acudiremos a la utilidad opendkim-genkey, de la siguiente forma: root@vps:/etc/opendkim/keys/iesgn19.es# opendkim-genkey -s pruebadkim -d iesgn19.es -b 1024 Donde: -s: Indicamos el nombre del selector para el que queremos generar el par de claves. En este caso, pruebadkim. -d: Indicamos el dominio que va a hacer uso del par de claves para firmar los correos. En este caso, iesgn19.es. -b: Indicamos el tamaño de la clave, que no deberá ser demasiado grande, para evitar las limitaciones de los registros TXT en el DNS. En este caso, 1024 bits. Una vez realizada la ejecución de dicho comando, habrán aparecido dos nuevos ficheros en el directorio actual, que podremos verificar listando el contenido del mismo, haciendo para ello uso del comando: root@vps:/etc/opendkim/keys/iesgn19.es# ls -l total 8 -rw------- 1 root root 1679 Feb 15 16:36 pruebadkim.private -rw------- 1 root root 512 Feb 15 16:36 pruebadkim.txt Donde: pruebadkim.private: Contiene la clave privada y será utilizada para firmar los correos electrónicos salientes. pruebadkim.txt: Contiene la clave pública y será utilizada por los servidores de correo para verificar la firma sobre los correos entrantes. Únicamente nos faltaría añadir el correspondiente registro TXT a nuestra zona DNS que contenga dicha clave pública para poder empezar a hacer uso de este mecanismo, así que antes de nada, tendremos que visualizarla, ejecutando para ello el comando: root@vps:/etc/opendkim/keys/iesgn19.es# cat pruebadkim.txt pruebadkim._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhxmxXURJ3QSnnRu4SW9aK3o2Uq8CNwckIzZTdrnA7tWhi1NXrpxPfx0EHOmF1LuJC23eSLbbmy5/xyT6hEnSToE3eNHHd+ZYezmVzi2lZtyoeqxWao15q4WWYxvF99AxLNg3CnXDxuh4T5wtMXBlcysn38iMsTQI+VnGFUxu9xQIDAQAB" ) ; ----- DKIM key pruebadkim for iesgn19.es Es importante mencionar que el nombre del registro TXT debe estar compuesto por [selector]._domainkey, que en mi caso sería pruebadkim._domainkey y quedaría finalmente de la siguiente forma: Es recomendable una vez realizada la modificación en la zona DNS llevar a cabo una comprobación utilizando para ello una herramienta externa que compruebe que el registro TXT es correcto, que nos devolverá unos resultados fácilmente interpretables: Como se puede apreciar, en mi caso, el registro TXT ha sido correctamente definido, sin embargo, esto no significa que mi máquina sea capaz de enviar firmados los correos salientes, ya que la herramienta previamente mencionada únicamente lleva a cabo comprobaciones sobre la zona DNS. Para verificar que nuestro nuevo mecanismo de autenticación está funcionando correctamente, tendremos que reiniciar los servicios Postfix y opendkim para así aplicar los cambios llevados a cabo, ejecutando para ello el comando: root@vps:/etc/opendkim/keys/iesgn19.es# systemctl restart opendkim postfix En consecuencia de los nuevos cambios aplicados, se habrá abierto un socket TCP/IP en el puerto 8892 que estará escuchando peticiones en la interfaz loopback, así que para verificarlo haremos uso del comando: root@vps:/etc/opendkim/keys/iesgn19.es# netstat -tlnp | egrep opendkim tcp 0 0 127.0.0.1:8892 0.0.0.0:* LISTEN 6851/opendkim Efectivamente, el proceso está escuchando peticiones tal y como debería, de manera que todo está listo para enviar un correo al exterior. En este caso, voy a enviar un correo a la dirección check-auth@verifier.port25.com, que me responderá con otro correo electrónico mostrando un resumen de los resultados de las pruebas llevadas a cabo sobre el mismo, entre las que se encuentran una comprobación del mecanismo SPF y otra para DKIM. En mi caso, el resultado obtenido es el siguiente: This message is an automatic response from Port25's authentication verifier service at verifier.port25.com. The service allows email senders to perform a simple check of various sender authentication mechanisms. It is provided free of charge, in the hope that it is useful to the email community. While it is not officially supported, we welcome any feedback you may have at &lt;verifier-feedback@port25.com&gt;. Thank you for using the verifier, The Port25 Solutions, Inc. team ========================================================== Summary of Results ========================================================== SPF check: pass "iprev" check: pass DKIM check: pass ========================================================== Details: ========================================================== HELO hostname: vps.iesgn19.es Source IP: 51.210.109.246 mail-from: root@iesgn19.es ---------------------------------------------------------- Como se puede apreciar, las directivas SPF check y DKIM check tienen el valor pass, lo que significa que la validación del remitente ha sido efectiva gracias a los registros SPF y DKIM que previamente hemos definido. Comprobación de SPF Hasta ahora, todas las comprobaciones que se han llevado a cabo sobre los mecanismos SPF y DKIM han sido ejecutadas por los servidores de correo externos que recibían nuestros correos. Sin embargo, nosotros nos encontramos “expuestos”, al no haber implementado la comprobación de ningún tipo de autenticación de procedencia del correo, de manera que en este caso vamos a comprobar el registro SPF para aquellos correos entrantes. Para que Postfix lleve a cabo la comprobación del registro SPF del dominio origen del correo tendremos que instalar el paquete postfix-policyd-spf-python, pues es una funcionalidad extra que vamos a añadir a nuestro servidor de correo y viene empaquetado de una forma ajena. Para ello, ejecutaremos el comando: root@vps:~# apt install postfix-policyd-spf-python El hecho de haber instalado el nuevo paquete no supone que la comprobación del SPF ya se esté llevando a cabo, ya que para ello necesitamos modificar los ficheros de configuración de Postfix para añadir las directivas necesarias. El primero que modificaremos será /etc/postfix/master.cf, haciendo para ello uso del comando: root@vps:~# nano /etc/postfix/master.cf Dentro del mismo, tendremos que incluir la siguiente directiva: policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/bin/policyd-spf Gracias a la misma, se ejecutará un proceso en un socket UNIX que será el que se utilice para el análisis del SPF. De otro lado, todavía necesitamos indicarle a nuestro servidor de correo qué debe hacer con aquellos correos que pasen el filtro, realizando una modificación en el fichero /etc/postfix/main.cf, ejecutando para ello el comando: root@vps:~# nano /etc/postfix/main.cf Dentro del mismo, tendremos que incluir la siguiente directiva: policyd-spf_time_limit = 3600 smtpd_recipient_restrictions = check_policy_service unix:private/policyd-spf Gracias a la misma, hemos establecido un timeout, así como una restricción de manera que todos los correos entrantes han de pasar el filtro SPF o de lo contrario serán rechazados. Para verificar que nuestro nuevo mecanismo de autenticación está funcionando correctamente, tendremos que reiniciar el servicio Postfix para así aplicar los cambios llevados a cabo, haciendo para ello uso del comando: root@vps:~# systemctl restart postfix Una vez que el servicio ha sido correctamente reiniciado, he procedido a enviar desde mi correo personal un correo electrónico a root, para así verificar que la comprobación del SPF se lleva a cabo tal y como debería. No considero necesario mostrar el proceso de envío, sino los logs producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, ejecutaremos el comando: root@vps:~# cat /var/log/mail.log Feb 15 17:09:20 vps postfix/smtpd[7345]: connect from mail-wr1-f51.google.com[209.85.221.51] Feb 15 17:09:21 vps policyd-spf[7352]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.221.51; helo=mail-wr1-f51.google.com; envelope-from=avacaferreras@gmail.com; receiver=&lt;UNKNOWN&gt; Feb 15 17:09:21 vps postfix/smtpd[7345]: 1F3E4E1322: client=mail-wr1-f51.google.com[209.85.221.51] Feb 15 17:09:21 vps postfix/cleanup[7355]: 1F3E4E1322: message-id=&lt;CANR0p-1iDjvsBA3adavyRhB2uojRDW+yHFF-qFcqYNki-9k4mg@mail.gmail.com&gt; Feb 15 17:09:21 vps opendkim[6851]: 1F3E4E1322: s=20161025 d=gmail.com SSL Feb 15 17:09:21 vps postfix/qmgr[7293]: 1F3E4E1322: from=&lt;avacaferreras@gmail.com&gt;, size=2899, nrcpt=1 (queue active) Feb 15 17:09:21 vps postfix/local[7356]: 1F3E4E1322: to=&lt;root@iesgn19.es&gt;, relay=local, delay=0.39, delays=0.38/0.01/0/0, dsn=2.0.0, status=sent (delivered to mailbox) Feb 15 17:09:21 vps postfix/qmgr[7293]: 1F3E4E1322: removed Feb 15 17:09:21 vps postfix/smtpd[7345]: disconnect from mail-wr1-f51.google.com[209.85.221.51] ehlo=2 starttls=1 mail=1 rcpt=1 bdat=1 quit=1 commands=7 Como se puede apreciar en la segunda línea del resultado de la ejecución de dicho comando, el resultado del filtro ha sido correcto (Received-SPF: Pass), de manera que podemos asegurar que nuestro servidor de correo está teniendo en cuenta los registros SPF de los dominios de los correos entrantes. Configuración de antispam Los mensajes de correo no deseados (spam) están a la orden del día, de manera que siempre es conveniente aplicar algún que otro filtro que controle la recepción de dicho tipo de correos, para así evitar inundar nuestra bandeja de entrada de correo inútil. En este caso, vamos a hacer uso de SpamAssassin, un filtro de correo que añadiremos a Postfix, cuya principal intención como se puede suponer, es detectar mediante una serie de tests y tratar de una determinada forma el correo spam. Para el correcto funcionamiento de SpamAssassin tendremos que instalar los paquetes spamassassin y spamc, haciendo para ello uso del comando: root@vps:~# apt install spamassassin spamc En mi caso, una vez finalizada la instalación, el servicio spamassassin no se encontraba activo ni habilitado para arrancar junto al sistema, de manera que para solucionarlo ejecutaremos los siguientes comandos: root@vps:~# systemctl start spamassassin root@vps:~# systemctl enable spamassassin Como cualquier otro antispam, spamassassin hace uso de una base de datos para realizar los respectivos tests rutinarios cuando recibe un correo, de manera que tendremos que intentar que dicha base de datos se encuentre en todo momento lo más actualizada posible. Para ello, tendremos que llevar a cabo una pequeña modificación en el fichero /etc/default/spamassassin, haciendo para ello uso del comando: root@vps:~# nano /etc/default/spamassassin Dentro del mismo, encontraremos una directiva que tendrá la siguiente forma: CRON=0 Como se puede suponer, tendremos que modificar su valor a 1 para que actualice dicha base de datos una vez al día, que generalmente suele ocurrir durante la noche, ya que es cuando se supone que menos actividad hay, quedando el siguiente resultado final: CRON=1 El hecho de haber instalado el nuevo paquete no supone que la comprobación del spam ya se esté llevando a cabo, ya que para ello necesitamos modificar el fichero maestro de configuración de Postfix para añadir las directivas necesarias, ejecutando para ello el comando: root@vps:~# nano /etc/postfix/master.cf Dentro del mismo, encontraremos dos directivas que tendrán la siguiente forma: smtp inet n - y - - smtpd submission inet n - y - - smtpd Tendremos que llevar a cabo una pequeña modificación sobre dichas directivas, para así indicar que todo el correo pase por spamassassin para llevar a cabo, como es lógico, las comprobaciones necesarias. Además, añadiremos una nueva directiva para el correcto funcionamiento del sistema antispam, quedando de la siguiente forma: smtp inet n - y - - smtpd -o content_filter=spamassassin submission inet n - y - - smtpd -o content_filter=spamassassin spamassassin unix - n n - - pipe user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient} Toda la configuración referente a Postfix ha finalizado, sin embargo, todavía no hemos indicado la forma en la que se tratarán a los correos que se marquen como no deseados. Para ello, tendremos que modificar el fichero /etc/spamassassin/local.cf, cuya configuración puede ser todo lo compleja que deseemos para así afinarla a nuestras necesidades, haciendo para ello uso del comando: root@vps:~# nano /etc/spamassassin/local.cf En mi caso, me limitaré a modificar el asunto del correo en cuestión para añadirle el encabezado “****SPAM****”, de manera que tendré que localizar la directiva con la siguiente forma: # rewrite_header Subject *****SPAM***** Tras ello, tendremos que descomentarla, quedando de la siguiente manera: rewrite_header Subject *****SPAM***** Para verificar que nuestro nuevo mecanismo antispam está funcionando correctamente, tendremos que reiniciar los servicios Postfix y spamassassin para así aplicar los cambios llevados a cabo, ejecutando para ello el comando: root@vps:~# systemctl restart postfix spamassassin Una vez que los servicios han sido correctamente reiniciados, he procedido a enviar desde mi correo personal un correo electrónico a root, para así verificar que la comprobación del spam se lleva a cabo tal y como debería, indicando para ello en el cuerpo del mensaje un texto que se utiliza para comprobar la eficacia de dichos sistemas: XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X No considero necesario mostrar el proceso de envío, sino los logs producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando: root@vps:~# cat /var/log/mail.log Feb 15 17:42:08 vps postfix/smtpd[8886]: connect from mail-wm1-f52.google.com[209.85.128.52] Feb 15 17:42:08 vps policyd-spf[8894]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.128.52; helo=mail-wm1-f52.google.com; envelope-from=avacaferreras@gmail.com; receiver=&lt;UNKNOWN&gt; Feb 15 17:42:08 vps postfix/smtpd[8886]: F3106E1325: client=mail-wm1-f52.google.com[209.85.128.52] Feb 15 17:42:08 vps postfix/cleanup[8898]: F3106E1325: message-id=&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG=qtg+1VQg@mail.gmail.com&gt; Feb 15 17:42:08 vps opendkim[6851]: F3106E1325: s=20161025 d=gmail.com SSL Feb 15 17:42:09 vps postfix/qmgr[8769]: F3106E1325: from=&lt;avacaferreras@gmail.com&gt;, size=2908, nrcpt=1 (queue active) Feb 15 17:42:09 vps spamd[8772]: spamd: connection from ::1 [::1]:34624 to port 783, fd 5 Feb 15 17:42:09 vps spamd[8772]: spamd: setuid to debian-spamd succeeded Feb 15 17:42:09 vps spamd[8772]: spamd: processing message &lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG=qtg+1VQg@mail.gmail.com&gt; for debian-spamd:114 Feb 15 17:42:09 vps postfix/smtpd[8886]: disconnect from mail-wm1-f52.google.com[209.85.128.52] ehlo=2 starttls=1 mail=1 rcpt=1 bdat=1 quit=1 commands=7 Feb 15 17:42:09 vps spamd[8772]: spamd: identified spam (999.9/5.0) for debian-spamd:114 in 0.2 seconds, 2979 bytes. Feb 15 17:42:09 vps spamd[8772]: spamd: result: Y 999 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,GTUBE,HTML_MESSAGE,RCVD_IN_MSPIKE_H2,SPF_PASS,TVD_SPACE_RATIO scantime=0.2,size=2979,user=debian-spamd,uid=114,required_score=5.0,rhost=::1,raddr=::1,rport=34624,mid=&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG=qtg+1VQg@mail.gmail.com&gt;,autolearn=no autolearn_force=no Feb 15 17:42:09 vps postfix/pickup[8768]: 4119BE13D0: uid=114 from=&lt;avacaferreras@gmail.com&gt; Feb 15 17:42:09 vps postfix/cleanup[8898]: 4119BE13D0: message-id=&lt;CANR0p-3rtCkGzxQfesenfvWfO-gL_2Q6duu-U2pWG=qtg+1VQg@mail.gmail.com&gt; Feb 15 17:42:09 vps postfix/pipe[8899]: F3106E1325: to=&lt;root@iesgn19.es&gt;, relay=spamassassin, delay=0.36, delays=0.13/0/0/0.23, dsn=2.0.0, status=sent (delivered via spamassassin service) Feb 15 17:42:09 vps postfix/qmgr[8769]: F3106E1325: removed Feb 15 17:42:09 vps postfix/qmgr[8769]: 4119BE13D0: from=&lt;avacaferreras@gmail.com&gt;, size=6217, nrcpt=1 (queue active) Feb 15 17:42:09 vps postfix/local[8903]: 4119BE13D0: to=&lt;root@iesgn19.es&gt;, relay=local, delay=0.01, delays=0.01/0/0/0, dsn=2.0.0, status=sent (delivered to mailbox) Feb 15 17:42:09 vps postfix/qmgr[8769]: 4119BE13D0: removed Feb 15 17:42:09 vps spamd[8695]: prefork: child states: II Como se puede apreciar en el resultado de la ejecución de dicho comando, primero se ha comprobado el SPF del remitente y una vez que se ha validado, el mensaje ha pasado a spamd, el cuál ha determinado que el mensaje es spam (identified spam), asignándole a su vez una puntuación (que en este caso es la más alta, ya que el mensaje está pensado para ello). A pesar de ello y gracias a la configuración establecida, el correo ha sido entregado en el buzón de igual forma. Para vericarlo, volveremos a hacer uso de la utilidad de línea de comandos, recibiendo por pantalla el siguiente resultado: root@vps:~# mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/root": 6 messages 2 new 6 unread U 1 root@iesgn19.es Thu Jan 21 09:33 23/693 Cron &lt;root@vps&gt; date U 2 root@iesgn19.es Thu Jan 21 09:34 23/693 Cron &lt;root@vps&gt; date U 3 root@iesgn19.es Thu Jan 21 09:35 23/693 Cron &lt;root@vps&gt; date U 4 avacaferreras@gma Mon Feb 15 17:09 61/3136 Prueba SPF &gt;N 5 auth-results@veri Mon Feb 15 17:11 232/9769 Authentication Report N 6 avacaferreras@gma Mon Feb 15 17:42 128/6215 *****SPAM***** Prueba SPAM Efectivamente, el último mensaje recibido ha sido marcado como spam por spamassassin y por tanto, se le ha añadido la cabecera “***SPAM***” al asunto de dicho mensaje, para identificarlo fácilmente. Esta medida es una de las menos restrictivas, ya que lo único que hace es marcar los mensajes detectados como spam, aunque en caso de así necesitarlo, podríamos descartar directamente dichos correos para que no se entregasen en el buzón. Configuración de antivirus Los mensajes de correo con virus están a la orden del día, de manera que siempre es conveniente aplicar algún que otro filtro que controle la recepción de dicho tipo de correos, para así evitar inundar nuestra bandeja de entrada de correo inútil. En este caso, vamos a hacer uso de ClamAV, un filtro de correo que añadiremos a Postfix, cuya principal intención como se puede suponer, es detectar mediante una serie de tests y tratar de una determinada forma el correo infectado. Para el correcto funcionamiento de ClamAV tendremos que instalar los paquetes clamsmtp y clamav-daemon, así como una serie de paquetería que permite tratar los ficheros comprimidos, para así poder detectar también virus en los mismos, ejecutando para ello el comando: root@vps:~# apt install clamsmtp clamav-daemon arc arj bzip2 cabextract lzop nomarch p7zip pax tnef unrar-free unzip El paquete clamsmtp ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto un socket TCP/IP en el puerto 25 que estará escuchando peticiones en la interfaz loopback, así que para verificarlo haremos uso del comando: root@vps:~# netstat -tlnp | egrep clamsmtp tcp 0 0 127.0.0.1:10026 0.0.0.0:* LISTEN 11870/clamsmtpd Efectivamente, el proceso está escuchando peticiones tal y como debería. En mi caso, una vez finalizada la instalación, el demonio clamav-daemon no se encontraba activo, de manera que para solucionarlo haremos uso del siguiente comando: root@vps:~# systemctl start clamav-daemon El hecho de haber instalado el nuevo paquete no supone que la comprobación de los virus ya se esté llevando a cabo, ya que para ello necesitamos modificar los ficheros de configuración de Postfix para añadir las directivas necesarias. El primero que modificaremos será /etc/postfix/master.cf, haciendo para ello uso del comando: root@vps:~# nano /etc/postfix/master.cf Dentro del mismo, tendremos que incluir las siguientes directivas: scan unix - - n - 16 smtp -o smtp_data_done_timeout=1200 -o smtp_send_xforward_command=yes -o disable_dns_lookups=yes 127.0.0.1:10025 inet n - n - 16 smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_client_restrictions= -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks_style=host -o smtpd_authorized_xforward_hosts=127.0.0.0/8 Gracias a la misma, habremos indicado que todo el correo pase por clamav para llevar a cabo, como es lógico, las comprobaciones necesarias. De otro lado, todavía necesitamos indicarle a nuestro servidor de correo dónde debe comunicarse con el servicio encargado de llevar a cabo las comprobaciones sobre dichos correos, realizando una modificación en el fichero /etc/postfix/main.cf, ejecutando para ello el comando: root@vps:~# nano /etc/postfix/main.cf Dentro del mismo, tendremos que incluir la siguiente directiva: content_filter = scan:127.0.0.1:10026 Para verificar que nuestro nuevo mecanismo antivirus está funcionando correctamente, tendremos que reiniciar el servicio Postfix para así aplicar los cambios llevados a cabo, ejecutando para ello el comando: root@vps:~# systemctl restart postfix Una vez que el servicio ha sido correctamente reiniciado, he procedido a enviar desde mi correo personal un correo electrónico a root, para así verificar que la comprobación de los virus se lleva a cabo tal y como debería, indicando para ello en el cuerpo del mensaje un texto que se utiliza para comprobar la eficacia de dichos sistemas: X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* No considero necesario mostrar el proceso de envío, sino los logs producidos por dicha recepción, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando: Feb 15 18:15:49 vps postfix/smtpd[12662]: connect from mail-wm1-f54.google.com[209.85.128.54] Feb 15 18:15:49 vps policyd-spf[12665]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.128.54; helo=mail-wm1-f54.google.com; envelope-from=avacaferreras@gmail.com; receiver=&lt;UNKNOWN&gt; Feb 15 18:15:49 vps postfix/smtpd[12662]: E4F3CE13D1: client=mail-wm1-f54.google.com[209.85.128.54] Feb 15 18:15:49 vps postfix/cleanup[12669]: E4F3CE13D1: message-id=&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX=5CzM3PBg@mail.gmail.com&gt; Feb 15 18:15:49 vps opendkim[6851]: E4F3CE13D1: s=20161025 d=gmail.com SSL Feb 15 18:15:49 vps postfix/qmgr[12612]: E4F3CE13D1: from=&lt;avacaferreras@gmail.com&gt;, size=3230, nrcpt=1 (queue active) Feb 15 18:15:49 vps spamd[8772]: spamd: connection from ::1 [::1]:34804 to port 783, fd 5 Feb 15 18:15:49 vps spamd[8772]: spamd: setuid to debian-spamd succeeded Feb 15 18:15:49 vps spamd[8772]: spamd: processing message &lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX=5CzM3PBg@mail.gmail.com&gt; for debian-spamd:114 Feb 15 18:15:49 vps postfix/smtpd[12662]: disconnect from mail-wm1-f54.google.com[209.85.128.54] ehlo=2 starttls=1 mail=1 rcpt=1 bdat=1 quit=1 commands=7 Feb 15 18:15:50 vps spamd[8772]: spamd: clean message (-0.1/5.0) for debian-spamd:114 in 0.2 seconds, 3297 bytes. Feb 15 18:15:50 vps spamd[8772]: spamd: result: . 0 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,HTML_MESSAGE,RCVD_IN_MSPIKE_H2,SPF_PASS,TVD_SPACE_RATIO scantime=0.2,size=3297,user=debian-spamd,uid=114,required_score=5.0,rhost=::1,raddr=::1,rport=34804,mid=&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX=5CzM3PBg@mail.gmail.com&gt;,autolearn=ham autolearn_force=no Feb 15 18:15:50 vps postfix/pickup[12611]: 34FC3E14DC: uid=114 from=&lt;avacaferreras@gmail.com&gt; Feb 15 18:15:50 vps postfix/cleanup[12669]: 34FC3E14DC: message-id=&lt;CANR0p-1KHMr3jt8FPXziTmAwk-nOxs0Cx-CdTHEiX=5CzM3PBg@mail.gmail.com&gt; Feb 15 18:15:50 vps postfix/pipe[12670]: E4F3CE13D1: to=&lt;root@iesgn19.es&gt;, relay=spamassassin, delay=0.32, delays=0.08/0/0/0.24, dsn=2.0.0, status=sent (delivered via spamassassin service) Feb 15 18:15:50 vps postfix/qmgr[12612]: E4F3CE13D1: removed Feb 15 18:15:50 vps opendkim[6851]: 34FC3E14DC: s=20161025 d=gmail.com SSL Feb 15 18:15:50 vps spamd[8695]: prefork: child states: II Feb 15 18:15:50 vps postfix/qmgr[12612]: 34FC3E14DC: from=&lt;avacaferreras@gmail.com&gt;, size=3806, nrcpt=1 (queue active) Feb 15 18:15:50 vps clamsmtpd: 100007: accepted connection from: 127.0.0.1 Feb 15 18:15:50 vps postfix/smtpd[12679]: connect from localhost[127.0.0.1] Feb 15 18:15:50 vps postfix/smtpd[12679]: 4BCE6E13D1: client=localhost[127.0.0.1] Feb 15 18:15:50 vps postfix/smtp[12677]: 34FC3E14DC: to=&lt;root@iesgn19.es&gt;, relay=127.0.0.1[127.0.0.1]:10026, delay=0.1, delays=0.05/0/0.04/0, dsn=2.0.0, status=sent (250 Virus Detected; Discarded Email) Feb 15 18:15:50 vps postfix/qmgr[12612]: 34FC3E14DC: removed Feb 15 18:15:50 vps clamsmtpd: 100007: from=avacaferreras@gmail.com, to=root@iesgn19.es, status=VIRUS:Eicar-Signature Feb 15 18:15:50 vps postfix/smtpd[12679]: disconnect from localhost[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 rset=1 quit=1 commands=6 Como se puede apreciar en el resultado de la ejecución de dicho comando, primero se ha comprobado el SPF del remitente y una vez que se ha validado, el mensaje ha pasado a spamd, el cuál ha determinado que el mensaje tampoco es spam (clean message), pasando por último a actuar clamsmtpd, que finalmente ha descartado el mensaje al detectar que se trata de un virus (250 Virus Detected; Discarded Email). Configuración de Maildir Aunque no lo hayamos mencionado con profundidad hasta ahora, el tipo de buzón que por defecto utiliza Postfix es mbox, aquel que almacena todos los mensajes en un fichero y que se utiliza para el protocolo POP3, en el que se descargan todos los correos desde el servidor. Sin embargo, la idea de este artículo es dejar de hacer uso del servidor de correo de forma local, y empezar a utilizar un cliente que se conecte al mismo mediante el protocolo SMTP para el envío de correos y mediante el protocolo IMAP para la sincronización de los mismos, dadas las ventajas que éste último nos ofrece. La característica del protocolo IMAP es que únicamente permite la sincronización con los buzones de tipo Maildir, aquel que almacena los mensajes en un directorio con sus correspondientes subdirectorios. Para llevar a cabo el cambio de buzón de tipo mbox a uno de tipo Maildir, tendremos que modificar el fichero de configuración principal de Postfix, ejecutando para ello el comando: root@vps:~# nano /etc/postfix/main.cf Dentro del mismo, tendremos que añadir una línea de la siguiente forma: home_mailbox = Maildir/ Gracias a la misma, una vez que reiniciemos el servicio y los cambios surtan efecto, los nuevos mensajes de correo recibidos se almacenarán de forma automática en un directorio de nombre Maildir/ en el directorio personal de cada uno de los usuarios existentes en la máquina, de manera que reiniciaremos el servicio haciendo para ello uso del comando: root@vps:~# systemctl restart postfix A partir de este momento, no podremos leer los correos recibidos a través de la herramienta de línea de comandos mail, ya que no soporta de forma nativa este tipo de buzón. Para solventarlo, tendremos que instalar un nuevo cliente de línea de comandos, como por ejemplo mutt, ejecutando para ello el comando: root@vps:~# apt install mutt A pesar de haber instalado el nuevo cliente, este no se encuentra todavía configurado para buscar los mensajes de correo dentro de ~/Maildir/, pues tendremos que indicarle dicho directorio de forma explícita en un fichero de nombre ~/.muttrc, haciendo para ello uso del comando: root@vps:~# nano ~/.muttrc Dentro del mismo, introduciremos el siguiente contenido: set mbox_type=Maildir set folder="~/Maildir" set mask="!^\\.[^.]" set mbox="~/Maildir" set record="+.Sent" set postponed="+.Drafts" set spoolfile="~/Maildir" Nota: En caso de haber indicado un nombre de directorio diferente a ~/Maildir en la configuración de Postfix, deberá modificarse la ruta del mismo a la hora de establecer el valor de las directivas mostradas. Toda la configuración necesaria para hacer uso del nuevo tipo de buzón ha finalizado, de manera que he procedido a enviar desde mi correo personal un correo electrónico a root, para así verificar que los nuevos correos entrantes se sitúan en un directorio de nombre ~/Maildir, tal y como deberían. No considero necesario mostrar el proceso de envío, sino el nuevo contenido del directorio mencionado producido por dicha recepción, concretamente en el subdirectorio de nombre new/, que es donde podremos encontrar información relevante al respecto. Para ello, haremos uso del comando: root@vps:~# ls -l Maildir/new/ total 4 -rw------- 1 root root 3548 Feb 16 08:20 1613460040.V801I42733M857089.vps Como era de esperar y como resultado de la recepción de un nuevo correo electrónico, ha aparecido un nuevo fichero en el correspondiente directorio del usuario root que hace referencia al mismo. Tal y como ya hemos mencionado, no podremos visualizar el contenido de dicho correo con la herramienta mail, sino que procederemos a usar el nuevo cliente de línea de comandos mutt para ello, ejecutando por lo tanto el comando: root@vps:~# mutt Tras ello, se nos mostrará lo siguiente por pantalla: Efectivamente, únicamente se ha mostrado un correo electrónico con asunto “Prueba mutt”, correspondiente al único fichero existente en el directorio ~/Maildir/new/, de manera que pulsaremos INTRO para visualizar su contenido y así verificar su correcto funcionamiento: Como era de esperar, el contenido del correo electrónico recibido ha sido correctamente mostrado, por lo que podemos concluir que tanto el nuevo tipo de buzón como el nuevo cliente de línea de comandos están funcionando tal y como deberían. Recepción de correos desde el cliente Como previamente hemos mencionado, si utilizamos un cliente de correo (MUA) externo para leer el correo guardado en el servidor de correos podemos usar dos protocolos de comunicación: POP3 (Post Office Protocol): Protocolo para recuperar correos electrónicos de un MDA. Su principal característica es que se descargan todos los correos. IMAP (Internet Message Access Protocol): Protocolo para recuperar correos electrónicos de un MDA. A diferencia del anterior, se sincroniza el estado de los correos entre el servidor y el cliente. En nuestro caso, vamos a trabajar con IMAP, que es el protocolo actualmente utilizado para poder leer nuestro correo desde distintos clientes de correos al no descargar todos los correos del buzón, como ocurre con POP3. Además, por seguridad, es recomendable que la conexión a través de estos protocolos, sea cuál sea el que hayamos elegido, se establezca de forma autenticada y cifrada. Por defecto, el protocolo IMAP hace uso de puerto 143/TCP y lleva a cabo una conexión autenticada, sin embargo, no se encuentra cifrada. Para cifrar dicha comunicación, podemos acudir a las siguientes alternativas: IMAP con STARTTLS: STARTTLS transforma una conexión insegura en una segura mediante el uso de SSL/TLS, consiguiendo por tanto tener una conexión cifrada a través del puerto 143/TCP. IMAPS: Versión segura del protocolo IMAP que hace uso del puerto 993/TCP. Como es lógico, necesitaremos además un servicio como dovecot que actúe como servidor IMAP, por lo que procederemos a su instalación haciendo para ello uso del comando: root@vps:~# apt install dovecot-imapd El paquete dovecot-imapd ha sido instalado en la máquina, existiendo en consecuencia un proceso que está actualmente en ejecución, que habrá abierto dos sockets TCP/IP en los puertos por defecto de los protocolos IMAP e IMAPS (143 y 993) que estará escuchando peticiones en todas las interfaces de la máquina (0.0.0.0), así que para verificarlo haremos uso del comando: root@vps:~# netstat -tlnp | egrep dovecot tcp 0 0 0.0.0.0:993 0.0.0.0:* LISTEN 19744/dovecot tcp 0 0 0.0.0.0:143 0.0.0.0:* LISTEN 19744/dovecot tcp6 0 0 :::993 :::* LISTEN 19744/dovecot tcp6 0 0 :::143 :::* LISTEN 19744/dovecot Efectivamente, el proceso está escuchando peticiones tal y como debería. Sin embargo, como bien sabemos, si queremos hacer uso de un protocolo cifrado, necesitaremos generar un certificado firmado por una autoridad certificadora de considerable reputación, así que en mi caso, he recurrido una vez más a Let’s Encrypt, siguiendo para ello los pasos indicados en el artículo Configuración de HTTPS en Nginx, pues mencionar aquí el proceso se saldría completamente del objetivo del post. Sin entrar en demasiado detalle, el funcionamiento del protocolo IMAPS es muy similar al del protocolo HTTPS, ya que contaremos con una clave privada y una clave pública (certificado), siendo esta última la que se envía al cliente que trata de realizar la conexión para así ponerse de acuerdo en la clave simétrica que se va a utilizar para cifrar la conexión, pues mantener en todo momento una conexión cifrada asimétricamente sería muy costoso computacionalmente. Cuando nuestro certificado para el nombre de dominio mail.iesgn19.es haya sido correctamente generado, todo estará listo para configurar dovecot para que haga uso de los mismos, de manera que comenzaremos por modificar el fichero /etc/dovecot/conf.d/10-ssl.conf, ejecutando para ello el comando: root@vps:~# nano /etc/dovecot/conf.d/10-ssl.conf Dentro del mismo, tendremos que localizar las directivas que por defecto tienen la siguiente forma: ssl_cert = &lt;/etc/dovecot/private/dovecot.pem ssl_key = &lt;/etc/dovecot/private/dovecot.key Donde: ssl_cert: Indicamos la ruta del certificado del servidor firmado por la CA. En este caso, /etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem. ssl_key: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, /etc/letsencrypt/live/mail.iesgn19.es/privkey.pem. El resultado final, tras llevar a cabo las correspondientes modificaciones sería: ssl_cert = &lt;/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem ssl_key = &lt;/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem A pesar de haber indicado ya el certificado que ha de usarse para la conexión cifrada, el servicio no se encuentra todavía configurado para buscar los mensajes de correo dentro de ~/Maildir/ para su correspondiente sincronización con el cliente, pues tendremos que indicarle dicho directorio de forma explícita en el fichero de nombre /etc/dovecot/conf.d/10-mail.conf, haciendo para ello uso del comando: root@vps:~# nano /etc/dovecot/conf.d/10-mail.conf Dentro del mismo, tendremos que localizar la directiva que por defecto tenga la siguiente forma: mail_location = mbox:~/mail:INBOX=/var/mail/%u En nuestro caso, al estar haciendo uso de buzones del tipo Maildir, tendremos que modificar dicha directiva para que finalmente tenga el siguiente valor: mail_location = maildir:~/Maildir Nuestra configuración del servicio dovecot para hacer uso del protocolo IMAP ha finalizado, de manera que tendremos que reiniciar dicho servicio para que los cambios llevados a cabo surtan efecto, ejecutando para ello el comando: root@vps:~# systemctl restart dovecot En lugar de llevar a cabo una prueba de dicho protocolo, vamos a realizar también las configuraciones necesarias para enviar correos desde un cliente, de manera que posteriormente realizaremos todos los tests oportunos de manera conjunta. Envío de correos desde el cliente Como previamente hemos mencionado, para el envío de correos entre MTA se utiliza el protocolo SMTP en el puerto 25/TCP, sin embargo, si vamos a utilizar un cliente remoto para el envío de correos, se utilizan dos opciones distintas: ESMTP + STARTTLS (Enhanced Simple Mail Transfer Protocol): STARTTLS transforma una conexión insegura en una segura mediante el uso de SSL/TLS, consiguiendo por tanto tener una conexión cifrada a través del puerto 587/TCP. Dicho puerto es conocido como puerto de submission o presentación. Al abrir este puerto, Postfix esta funcionando como MSA (Mail Submission Agent) que recibe mensajes de correo electrónico desde un MUA (Mail User Agent) y coopera con un MTA (Mail Transport Agent) para entregar el correo. Tenemos que conseguir que la comunicación que se establece desde el cliente al servidor sea autenticada. Para ello, utilizamos SASL (Simple Authentication and Security Layer), un framework para autenticación y autorización en protocolos de Internet. Para realizar la autenticación vamos a usar dovecot (que ya tiene un mecanismo de autenticación). De otro lado, tenemos que conseguir además que la comunicación sea cifrada, para ello vamos a utilizar STARTTLS que nos permite que utilizando el mismo puerto (587/TCP), la conexión sea cifrada. SMTPS (Simple Mail Transfer Protocol Secure): Protocolo para conseguir el cifrado de la comunicación entre el cliente y el servidor. Utiliza el puerto 465/TCP. No es una extensión de SMTP. Es muy parecido a HTTPS. En este caso, vamos a reutilizar el certificado generado en el anterior apartado para cifrar la conexión SMTP entre el cliente y el servidor, de manera que todo estará listo para configurar postfix para que haga uso de los mismos, de manera que comenzaremos por modificar el fichero /etc/postfix/main.cf, ejecutando para ello el comando: root@vps:~# nano /etc/postfix/main.cf Dentro del mismo, tendremos que localizar las directivas que por defecto tienen la siguiente forma: smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key Donde: smtpd_tls_cert_file: Indicamos la ruta del certificado del servidor firmado por la CA. En este caso, /etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem. smtpd_tls_key_file: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, /etc/letsencrypt/live/mail.iesgn19.es/privkey.pem. Además de indicar las rutas de los determinados ficheros, tendremos que añadir una serie de directivas para el correcto funcionamiento del sistema de autenticación por parte de dovecot. El resultado final, tras llevar a cabo las correspondientes modificaciones sería: smtpd_tls_cert_file=/etc/letsencrypt/live/mail.iesgn19.es/fullchain.pem smtpd_tls_key_file=/etc/letsencrypt/live/mail.iesgn19.es/privkey.pem smtpd_sasl_auth_enable = yes smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_authenticated_header = yes broken_sasl_auth_clients = yes A pesar de haber indicado ya el certificado que ha de usarse para la conexión cifrada, el servicio no se encuentra todavía configurado para utilizar los puertos 587/TCP y 465/TCP, pues tendremos que indicárselo de forma explícita en el fichero de nombre /etc/postfix/master.cf, haciendo para ello uso del comando: root@vps:~# nano /etc/postfix/master.cf Dentro del mismo, tendremos que buscar las directivas submission y smtps y descomentarlas al completo, quedando de la siguiente forma: submission inet n - y - - smtpd -o content_filter=spamassassin -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_reject_unlisted_recipient=no -o smtpd_client_restrictions=$mua_client_restrictions -o smtpd_helo_restrictions=$mua_helo_restrictions -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions= -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING smtps inet n - y - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_reject_unlisted_recipient=no -o smtpd_client_restrictions=$mua_client_restrictions -o smtpd_helo_restrictions=$mua_helo_restrictions -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions= -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING Si recordamos, a la hora de especificarle la ruta de los ficheros del certificado a Postfix, hemos introducido las directivas necesarias para que dovecot pueda realizar la autenticación, sin embargo, todavía faltaría indicarle a dovecot cómo tiene que realizar dicha autenticación, de manera que procederemos a modificar el fichero /etc/dovecot/conf.d/10-master.conf para así poder interconectar ambos servicios, ejecutando para ello el comando: root@vps:~# nano /etc/dovecot/conf.d/10-master.conf Dentro del mismo, tendremos que localizar la directiva que por defecto tenga la siguiente forma: unix_listener auth-userdb { } En este caso, la ruta del socket UNIX que se utilizará para la comunicación entre ambos servicios no es correcta, ni tampoco lo son los permisos asignados, de manera que tendremos que modificarla para que finalmente tenga el siguiente aspecto: unix_listener /var/spool/postfix/private/auth { mode = 0666 } Nuestra configuración de los servicios Postfix y dovecot para hacer uso del protocolo SMTP de forma segura ha finalizado, de manera que tendremos que reiniciar dichos servicios para que los cambios llevados a cabo surtan efecto, haciendo para ello uso del comando: root@vps:~# systemctl restart postfix dovecot En consecuencia de los nuevos cambios aplicados, se habrán abierto dos sockets TCP/IP en los puertos 587 y 465 que estarán escuchando peticiones en todas las interfaces de la máquina (0.0.0.0), así que para verificarlo haremos uso del comando: root@vps:~# netstat -tln | egrep '(587|465)' tcp 0 0 0.0.0.0:587 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:465 0.0.0.0:* LISTEN tcp6 0 0 :::587 :::* LISTEN tcp6 0 0 :::465 :::* LISTEN Efectivamente, el proceso está escuchando peticiones tal y como debería, de manera que todo está listo para enviar un correo desde un cliente externo. Configuración de cliente Thunderbird Una vez configurados los dos protocolos necesarios para recibir y enviar correos desde un cliente externo, pues hasta ahora hemos estado llevando a cabo ambas acciones directamente desde el servidor, vamos a proceder a la configuración de un cliente Thunderbird, que nos permitirá llevar a cabo dichas acciones cotidianas de una forma mucho más “amigable” que una utilidad de línea de comandos. El primer paso, como es lógico, consistirá en instalar el paquete thunderbird, aunque podríamos hacer uso de otro cliente distinto como Evolution, que viene instalado por defecto. Para ello, ejecutaremos el comando: root@debian:~# apt install thunderbird Tras ello, podremos ejecutar la aplicación y se nos abrirá una ventana emergente en la que podremos llevar a cabo la configuración inicial con nuestro servidor de correo. En mi caso, el resultado final sería el siguiente: Como se puede apreciar, hemos establecido una configuración básica como el nombre a mostrar en los correos enviados, la dirección de correo electrónico, la contraseña, el servidor, los puertos a utilizar para cada uno de los protocolos… Una vez establecida la configuración correcta, pulsaremos en “Done” para así añadir la cuenta de correo electronico a nuestro cliente gráfico. En mi caso, he procedido a enviar desde mi correo personal un correo electrónico a debian, para así verificar que la recepción del mismo se lleva a cabo tal y como debería. No considero necesario mostrar el proceso de envío, sino el resultado final producido por dicha recepción, que sería el siguiente: Como era de esperar, el correo electrónico ha sido correctamente recibido en nuestro cliente Thunderbird, por lo que podemos concluir que el protocolo IMAP se encuentra funcionando tal y como debería a través del puerto 993/TCP, de manera que se están sincronizando correctamente los mensajes de correo con el servidor. Todavía nos falta comprobar que el protocolo SMTP también está funcionando correctamente, de manera que llevaremos ahora la prueba a la inversa, enviando desde nuestro cliente un correo electrónico a mi correo personal, de la siguiente forma: Tras pulsar el botón “Send” no se me ha mostrado ningún error por pantalla, de manera que puedo decir con casi total seguridad que el envío se ha producido correctamente. Sin embargo, hasta que no lo verifiquemos en mi correo personal, no tendremos la certeza de ello: Efectivamente, el correo ha sido correctamente recibido en Gmail, por lo que podemos concluir que el protocolo SMTP se encuentra funcionando tal y como debería a través del puerto 465/TCP, de manera que se están enviando correctamente los mensajes de correo a través del servidor. Instalación de webmail En un ámbito empresarial, quizás puede ser más común el hecho de utilizar una aplicación centralizada que permita gestionar el correo del equipo mediante una interfaz web, ya que así no es necesario ir configurando el cliente para todos y cada uno de los equipos existentes en la empresa. Para alcanzar este propósito, he decidido hacer uso del servidor web nginx alojado en la máquina VPS que se encuentra actualmente escuchando peticiones en los puertos 80 (HTTP) y 443 (HTTPS), de manera que crearemos un VirtualHost accesible desde un determinado nombre de dominio y serviremos a través del mismo una aplicación web de correo (webmail), de nombre Roundcube. El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS iesgn19.es, concretamente nombrando un nuevo servicio webmail mediante un registro CNAME al registro A que apunta a la dirección 51.210.109.246, que será a través del cuál accederemos al VirtualHost en cuestión, tal y como podemos apreciar a continuación: Como bien sabemos, si queremos hacer uso del protocolo HTTPS para dicho nombre de dominio, necesitaremos generar un nuevo certificado firmado por una autoridad certificadora de considerable reputación, de la misma forma que previamente lo hemos hecho para el nombre mail.iesgn19.es. Cuando nuestro certificado haya sido correctamente generado, todo estará listo para configurar el nuevo VirtualHost dentro del directorio /etc/nginx/sites-available/, cuyo nombre he decidido que en este caso sea roundcube, de manera que ejecutaremos el comando: root@vps:~# nano /etc/nginx/sites-available/roundcube Dentro del mismo, tendremos que crear dos directivas server, una para el VirtualHost accesible en el puerto 80 HTTP, dentro del cuál únicamente asignaremos el ServerName correspondiente y una redirección permanente para así forzar HTTPS. En la otra, para el VirtualHost accesible en el puerto 443 HTTPS, tendremos que configurar las siguientes directivas: server_name: Indicaremos el nombre de dominio a través del cuál accederemos al servidor. ssl: Activa el motor SSL, necesario para hacer uso de HTTPS, por lo que su valor debe ser on. ssl_certificate: Indicamos la ruta del certificado del servidor firmado por la CA. En este caso, /etc/letsencrypt/live/webmail.iesgn19.es/fullchain.pem. ssl_certificate_key: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, /etc/letsencrypt/live/webmail.iesgn19.es/privkey.pem. El resultado final sería: server { listen 80; listen [::]:80; server_name webmail.iesgn19.es; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; ssl on; ssl_certificate /etc/letsencrypt/live/webmail.iesgn19.es/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/webmail.iesgn19.es/privkey.pem; server_name webmail.iesgn19.es; root /srv/roundcube; index index.php index.html index.htm index.nginx-debian.html; location / { try_files $uri $uri/ /index.php; } location ~ \.php { try_files $uri =404; fastcgi_pass unix:/run/php/php7.3-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /.well-known/acme-challenge { allow all; } location ~ ^/(README|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ { deny all; } location ~ ^/(bin|SQL)/ { deny all; } location ~* \.(jpg|jpeg|gif|png|webp|svg|woff|woff2|ttf|css|js|ico|xml)$ { access_log off; log_not_found off; expires 360d; } } Como se puede apreciar, el fichero de configuración final es bastante complejo, aunque para la tranquilidad de los lectores, no es para nada necesario comprender qué hacen las directivas contenidas para hacer uso de la aplicación web. Cuando hayamos finalizado, simplemente guardaremos los cambios en el fichero. Si nos fijamos, el directorio en el que debemos alojar todos los ficheros necesarios para el correcto funcionamiento de la aplicación es /srv/roundcube, directorio que todavía no ha sido generado, de manera que procederemos a ello haciendo uso del comando: root@vps:~# mkdir /srv/roundcube El directorio que contendrá todos los ficheros ya ha sido generado, de manera que para trabajar de una forma mucho más cómoda, nos moveremos dentro del mismo ejecutando para ello el comando: root@vps:~# cd /srv/roundcube/ En este caso, vamos a utilizar la última versión de Roundcube disponible (1.4.11), que podremos descargar desde el repositorio oficial, concretamente desde aquí. Para ello, haremos uso de wget para descargar el paquete comprimido de Roundcube desde dicha web: root@vps:/srv/roundcube# wget https://github.com/roundcube/roundcubemail/releases/download/1.4.11/roundcubemail-1.4.11-complete.tar.gz --2021-02-16 09:17:35-- https://github.com/roundcube/roundcubemail/releases/download/1.4.11/roundcubemail-1.4.11-complete.tar.gz Resolving github.com (github.com)... 140.82.121.4 Connecting to github.com (github.com)|140.82.121.4|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://github-releases.githubusercontent.com/4224042/fe83ab00-6a4d-11eb-8ff3-e714480567a4?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210216%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20210216T081557Z&amp;X-Amz-Expires=300&amp;X-Amz-Signature=08ea29d4650c0bea51417b17b9578fe657c5c6c87a05209f8991957d88123159&amp;X-Amz-SignedHeaders=host&amp;actor_id=0&amp;key_id=0&amp;repo_id=4224042&amp;response-content-disposition=attachment%3B%20filename%3Droundcubemail-1.4.11-complete.tar.gz&amp;response-content-type=application%2Foctet-stream [following] --2021-02-16 09:17:35-- https://github-releases.githubusercontent.com/4224042/fe83ab00-6a4d-11eb-8ff3-e714480567a4?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210216%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20210216T081557Z&amp;X-Amz-Expires=300&amp;X-Amz-Signature=08ea29d4650c0bea51417b17b9578fe657c5c6c87a05209f8991957d88123159&amp;X-Amz-SignedHeaders=host&amp;actor_id=0&amp;key_id=0&amp;repo_id=4224042&amp;response-content-disposition=attachment%3B%20filename%3Droundcubemail-1.4.11-complete.tar.gz&amp;response-content-type=application%2Foctet-stream Resolving github-releases.githubusercontent.com (github-releases.githubusercontent.com)... 185.199.108.154, 185.199.109.154, 185.199.110.154, ... Connecting to github-releases.githubusercontent.com (github-releases.githubusercontent.com)|185.199.108.154|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 7048262 (6.7M) [application/octet-stream] Saving to: ‘roundcubemail-1.4.11-complete.tar.gz’ roundcubemail-1.4.11-complete.tar.gz 100%[==========================&gt;] 6.72M 6.75MB/s in 1.0s 2021-02-16 09:17:37 (6.75 MB/s) - ‘roundcubemail-1.4.11-complete.tar.gz’ saved [7048262/7048262] Para verificar que el comprimido se ha descargado correctamente, listaremos el contenido del directorio actual haciendo para ello uso del comando ls -l: root@vps:/srv/roundcube# ls -l total 6884 -rw-r--r-- 1 root root 7048262 Feb 8 20:41 roundcubemail-1.4.11-complete.tar.gz Efectivamente, se ha descargado un paquete de nombre “roundcubemail-1.4.11-complete.tar.gz” con un peso total de 6.72 MB (7048262 bytes). Al estar comprimido el fichero, no podremos hacer uso de la aplicación hasta que no hagamos una extracción de los ficheros contenidos. Además, para liberar espacio, borraremos tras ello el fichero comprimido ya que no nos hará falta. Para ello, ejecutaremos el comando: root@vps:/srv/roundcube# tar -zxf roundcubemail-1.4.11-complete.tar.gz --strip 1 &amp;&amp; rm roundcubemail-1.4.11-complete.tar.gz Donde: -z: Utiliza gzip para descomprimir el fichero. -x: Indica a tar que desempaquete el fichero. -f: Indica a tar que el siguiente argumento es el nombre del fichero .tar.gz. -–strip 1: Saltamos el primer directorio, ya que dentro del comprimido hay un directorio padre que no necesitamos. Para verificar que el fichero se ha descomprimido correctamente, haremos uso del comando: root@vps:/srv/roundcube# ls -l total 404 drwxr-xr-x 2 501 80 4096 Feb 16 09:18 bin -rw-r--r-- 1 501 80 186666 Feb 8 20:29 CHANGELOG -rw-r--r-- 1 501 staff 911 Feb 8 20:29 composer.json -rw-r--r-- 1 501 80 943 Feb 8 20:29 composer.json-dist -rw-r--r-- 1 501 80 89041 Feb 8 20:29 composer.lock drwxr-xr-x 2 501 80 4096 Feb 16 09:18 config -rw-r--r-- 1 501 80 12843 Feb 8 20:29 index.php -rw-r--r-- 1 501 80 12864 Feb 8 20:29 INSTALL drwxr-xr-x 3 501 80 4096 Feb 16 09:18 installer -rw-r--r-- 1 501 80 35147 Feb 8 20:29 LICENSE drwxr-xr-x 2 501 80 4096 Feb 16 09:18 logs drwxr-xr-x 35 501 80 4096 Feb 16 09:18 plugins drwxr-xr-x 8 501 80 4096 Feb 16 09:18 program drwxr-xr-x 3 501 80 4096 Feb 16 09:18 public_html -rw-r--r-- 1 501 80 3810 Feb 8 20:29 README.md drwxr-xr-x 5 501 80 4096 Feb 16 09:18 skins drwxr-xr-x 7 501 80 4096 Feb 8 20:29 SQL drwxr-xr-x 2 501 80 4096 Feb 16 09:18 temp -rw-r--r-- 1 501 80 4148 Feb 8 20:29 UPGRADING drwxr-xr-x 9 501 80 4096 Feb 16 09:18 vendor Efectivamente, todo el contenido se ha descomprimido tal y como queríamos (en lugar de descomprimir un directorio de nombre roundcubemail-1.4.11 del que posteriormente tendríamos que mover los ficheros contenidos al directorio actual). Ya tenemos todos los ficheros necesarios para llevar a cabo la instalación de Roundcube, pero hay un pequeño detalle que todavía no hemos contemplado, pues el usuario y el grupo propietario correspondiente a dichos ficheros y directorios es root, por lo que procedemos a cambiar dicho propietario y grupo de forma recursiva a www-data, haciendo para ello uso del comando chown -R, pues de otro modo no podría escribir en dichos ficheros durante la instalación: root@vps:/srv/roundcube# chown -R www-data:www-data /srv/roundcube/ Listo, toda la configuración necesaria ya ha sido realizada, así que únicamente queda habilitar dicho sitio. Para activar el sitio, a diferencia de apache2 que contaba con una utilidad para ello, tendremos crear el enlace simbólico al fichero de configuración ubicado en /etc/nginx/sites-available/ dentro de /etc/nginx/sites-enabled/ de forma manual. Para ello, haremos uso del comando: root@vps:/srv/roundcube# ln -s /etc/nginx/sites-available/roundcube /etc/nginx/sites-enabled/ Al parecer, el sitio ha sido correctamente habilitado, pero para activar la nueva configuración, tendremos que volver a cargar la configuración del servicio nginx, ejecutando para ello el comando: root@vps:/srv/roundcube# systemctl reload nginx Nos falta un único paso para proceder con la instalación, y es que todavía no hemos creado la base de datos que utilizará, de manera que para acceder al motor de mysql, tendremos que hacer uso del comando: root@vps:/srv/roundcube# mysql -u root -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 116 Server version: 10.3.27-MariaDB-0+deb10u1 Debian 10 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. Cuando nos encontremos haciendo uso del motor mysql, procederemos a la creación de dicha base de datos, con nombre bd_roundcube, por ejemplo: MariaDB [(none)]&gt; CREATE DATABASE bd_roundcube; Query OK, 1 row affected (0.001 sec) La base de datos que usará Roundcube ya se encuentra creada, pero de nada nos sirve tener una base de datos si no tenemos un usuario que pueda acceder a la misma. En este caso, vamos a crear un usuario de nombre “user_roundcube” que tenga permitido el acceso desde localhost (es decir, desde la máquina local) y cuya contraseña sea “pass_roundcube” (lógicamente, en un caso real se usarían credenciales más seguras). Para ello, haremos uso del comando: MariaDB [(none)]&gt; CREATE USER 'user_roundcube'@'localhost' IDENTIFIED BY 'pass_roundcube'; Query OK, 0 rows affected (0.004 sec) El usuario ya ha sido generado, pero todavía no tiene permisos sobre la base de datos que acabamos de crear, así que debemos otorgarle dichos privilegios. Para ello, ejecutaremos el comando: MariaDB [(none)]&gt; GRANT ALL PRIVILEGES ON bd_roundcube.* TO 'user_roundcube'@'localhost'; Query OK, 0 rows affected (0.001 sec) Una vez realizadas todas las modificaciones oportunas, podremos salir de mysql haciendo uso del comando: MariaDB [(none)]&gt; quit Bye Nota: No es necesario hacer uso del comando FLUSH PRIVILEGES;, a diferencia de varios artículos que he estado leyendo en Internet, que usan dicho comando muy a menudo sin necesidad alguna. Dicho comando, lo que hace es recargar la caché de las tablas GRANT que se encuentran en memoria, recarga que se hace de forma automática al hacer uso de una sentencia GRANT, de manera que no es necesario hacerlo manualmente. Para aclararlo, dicho comando únicamente debe utilizarse tras modificar las tablas GRANT de manera indirecta, es decir, tras usar sentencias INSERT, UPDATE o DELETE. Antes de proceder con la correspondiente prueba de funcionamiento, tendremos que instalar dos librerías necesarias para el correcto funcionamiento de la aplicación web, ejecutando para ello el comando: root@vps:~# apt install php-intl php-imagick En un principio, todas las configuraciones necesarias han sido llevadas a cabo, así que es hora de realizar la correspondiente prueba de acceso. Para ello, abriremos un navegador web y trataremos de acceder a https://webmail.iesgn19.es/installer para proceder con la instalación del mismo. En caso de tener dudas sobre el proceso llevado a cabo, se recomienda llevar a cabo una lectura del artículo Instalación de un servidor LEMP en el que se tratan con mayor profundidad los pasos llevados a cabo durante la instalación. Dentro del instalador, como se puede suponer, tendremos que indicar el valor de algunas directivas necesarias para la instalación, como por ejemplo, la base de datos a utilizar por la aplicación web y las credenciales del usuario previamente generado: Tras ello, llegamos a la parte de configuración de los protocolos para la sincronización y envío de correos. Es importante comprender llegado este punto que la conexión entre el cliente y el servidor se está llevando a cabo actualmente de forma cifrada, mediante el uso del protocolo HTTPS en el puerto 443/TCP, ya que estamos haciendo uso de una aplicación web. Gracias al uso de dicho protocolo, la conexión entre el cliente y el servidor se encuentra cifrada, y teniendo también en cuenta que las peticiones al servidor de correos las estamos llevando a cabo de forma indirecta, ya que nosotros hacemos la petición a la aplicación web (cifrada) y es dicha aplicación la que se comunica con el servidor de correos de forma local, no será necesario utilizar las alternativas cifradas de los protocolos IMAP y SMTP, ya que como hemos mencionado, el uso de dichos protocolos se ejecuta de forma local, de manera que la conexión no sale de la máquina servidora y no es necesario cifrarla, evitando por tanto un consumo de recursos innecesario. En conclusión, para el protocolo IMAP haremos uso del puerto 143/TCP, tal y como podemos apreciar: Lo mismo ocurre con el protocolo SMTP, de manera que utilizaremos el puerto 25/TCP, de la siguiente forma: Por último, tendremos que indicar el idioma por defecto para la página. En mi caso, he optado por elegir el inglés (en_US), aunque podría haber elegido cualquier otro formateado de la forma indicada en el RFC1766: Tras ello, podremos continuar y en el último apartado de la instalación se nos mostrará lo siguiente: Como se puede apreciar, la base de datos no ha sido inicializada, en el sentido de que es necesario crear una serie de tablas dentro de la misma para que la aplicación web haga uso de las mismas, de manera que pulsaremos en “Initialize database”, obteniendo el siguiente resultado: Tras unos segundos, las tablas ya habrán sido generadas en la base de datos y todo estará listo para comenzar a utilizar la aplicación web, no sin antes eliminar el directorio installer/ actualmente existente dentro del DocumentRoot, para así evitar posibles vulnerabilidades, haciendo para ello uso del comando: root@vps:/srv/roundcube# rm -r installer/ Una vez eliminado el directorio, todo estará listo para acceder, ahora sí, a la página principal de la aplicación web, accediendo por tanto a https://webmail.iesgn19.es, pudiendo apreciar lo siguiente: Nos aparecerá un formulario para iniciar sesión, en el que debemos introducir el nombre de usuario generado en la máquina servidora junto a su contraseña, en este caso, debian: Efectivamente, el correo electrónico ha sido correctamente sincronizado con nuestra aplicación Roundcube, por lo que podemos concluir que el protocolo IMAP se encuentra funcionando tal y como debería a través del puerto 143/TCP, de manera que se están sincronizando correctamente los mensajes de correo con el servidor. Sin embargo, hay un pequeño fallo de configuración, y es que cuando enviamos un correo al exterior, se hace con el dominio @localhost como remitente, de manera que tendremos que modificarlo accediendo para ello al apartado Settings en el menú de la izquierda, seguido de Identities y pulsando en nuestra cuenta en cuestión. Una vez ahí, podremos establecer correctamente nuestro correo electrónico: Todavía nos falta comprobar que el protocolo SMTP también está funcionando correctamente, de manera que llevaremos ahora la prueba a la inversa, enviando desde nuestro cliente web un correo electrónico a mi correo personal, de la siguiente forma: Tras pulsar el botón “Send” no se me ha mostrado ningún error por pantalla, de manera que puedo decir con casi total seguridad que el envío se ha producido correctamente. Sin embargo, hasta que no lo verifiquemos en mi correo personal, no tendremos la certeza de ello: Efectivamente, el correo ha sido correctamente recibido en Gmail, por lo que podemos concluir que el protocolo SMTP se encuentra funcionando tal y como debería a través del puerto 25/TCP, de manera que se están enviando correctamente los mensajes de correo a través del servidor. Test final Por último, y a modo de comprobación, vamos a hacer uso de una herramienta para verificar que todo el trabajo llevado a cabo hasta ahora ha servido y los servidores de correo que reciban mensajes procedentes de nosotros, los tendrán en cuenta de una forma muy posiblemente favorable. Su funcionamiento es bastante sencillo a la vez que completo, consistente en enviar un correo electrónico a la dirección que se nos facilita por pantalla, tal y como se puede apreciar a continuación: En mi caso, he procedido a enviar desde mi cliente Thunderbird un correo electrónico a la dirección mostrada por pantalla, para así obtener una calificación resultante de llevar a cabo una serie de pruebas sobre el correo enviado. No considero necesario mostrar el proceso de envío, sino el resultado final producido por dicha recepción, que tras pulsar en “A continuación comprueba tu puntuación” sería el siguiente: Finalmente, la puntuación obtenida ha sido de 10/10, por lo que podemos concluir que hemos realizado un buen trabajo y los servidores de correo van a tratar de forma favorable los mensajes procedentes del nuestro. A pesar de ello, me ha indicado algunos aspectos que podría mejorar, como por ejemplo añadir un registro DMARC, así como un encabezado de anulación de suscripción a la lista, principalmente pensado para aquellas personas que envíen correos de forma masiva a través de newsletters, por lo que me voy con un muy buen sabor de boca.]]></summary></entry><entry><title type="html">Utilización de iSCSI en Linux y Windows</title><link href="https://www.alvarovf.com/hlc/2021/02/10/utilizacion-iscsi-linux-windows.html" rel="alternate" type="text/html" title="Utilización de iSCSI en Linux y Windows" /><published>2021-02-10T08:28:00+00:00</published><updated>2021-02-10T08:28:00+00:00</updated><id>https://www.alvarovf.com/hlc/2021/02/10/utilizacion-iscsi-linux-windows</id><content type="html" xml:base="https://www.alvarovf.com/hlc/2021/02/10/utilizacion-iscsi-linux-windows.html"><![CDATA[<p>El protocolo <strong>iSCSI</strong> es un protocolo que nos proporciona acceso a dispositivos de bloques sobre la red (TCP/IP). A diferencia de otros protocolos como NFS o Samba, no proporciona ficheros o directorios, sino dispositivos de bloques de forma íntegra, es decir, se conecta un nuevo disco duro y es posible compartir el disco duro en bruto a través de la red, habilitando su uso de forma remota, sin necesidad de crear una tabla de particiones ni introducir un sistema de ficheros en el mismo, pues esa será labor del extremo que actúe como cliente, que tendrá control total sobre dicho dispositivo de bloques.</p>

<p>Otra de las grandes características es que nos permite montar el mismo dispositivo de bloques en varios equipos clientes de forma simultánea (puede llegar a provocar un problema de concurrencia que habría que solventar a otro nivel, pero como tal, está soportado), de una forma mucho más económica que <em>Fibre Channel</em>, teniendo en cuenta además que tiene soporte en la mayoría de sistemas operativos.</p>

<p>Dicho protocolo suele utilizarse en redes de almacenamiento, ya que nos proporcionan un aislamiento adecuado para compartir dichos dispositivos de una forma más segura, aunque tal y como veremos a continuación, dicho protocolo nos ofrece sus propios mecanismos de autenticación.</p>

<p>Antes de empezar a hacer uso de forma práctica de dicho protocolo, es necesario conocer una serie de términos:</p>

<ul>
  <li><strong>Unidad lógica (LUN)</strong>: Dispositivo de bloques a compartir por el servidor iSCSI (discos duros, particiones, volúmenes lógicos…).</li>
  <li><strong>Target</strong>: Recurso a compartir desde el servidor, que incluye una o varias <strong>LUN</strong>. Por ejemplo, podemos conectar a la máquina servidora 3 discos duros e incorporarlos dentro de un mismo <em>target</em>, al que posteriormente se conectará un cliente mediante una única conexión y podrá hacer uso de dichos discos. Dependiendo del caso, también podrían haberse creado 3 <em>targets</em>, uno por cada disco, y distribuirlos de distinta manera.</li>
  <li><strong>Initiator</strong>: Cliente iSCSI que realiza la conexión.</li>
  <li><strong>Multipath</strong>: Permite garantizar la disponibilidad del dispositivo de bloques remoto en caso de haber más de una ruta posible entre el <em>target</em> y el <em>initiator</em>, pues en caso de perder la conexión mediante una ruta, dicha conexión se mantendría a través de otra. Como es lógico, depende de la infraestructura que tengamos.</li>
  <li><strong>IQN</strong>: Formato utilizado para la descripción única de los recursos que se comparten. Se suele utilizar una regla para nombrarlos de la forma <em>iqn.(año)-(mes).(nombre de dominio invertido):(nombre único)</em>. Por ejemplo, <em>iqn.2021-02.com.alvarovf:target1</em>.</li>
  <li><strong>iSNS</strong>: Protocolo que permite gestionar recursos iSCSI como si fueran <em>Fibre Channel</em>.</li>
</ul>

<p>Una vez comprendido qué es el protocolo <strong>iSCSI</strong> y su funcionamiento de forma superficial, vamos a proceder a aclarar dichos conceptos de una forma más práctica. Para ello, he generado un pequeño escenario compuesto por las siguientes máquinas:</p>

<ul>
  <li><strong>iscsis1</strong>: Máquina <strong>Debian Buster</strong> conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.136</strong>. Actuará como servidor y tiene 3 discos duros de 1GB asociados.</li>
  <li><strong>iscsic1</strong>: Máquina <strong>Debian Buster</strong> conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.137</strong>. Actuará como cliente (<em>initiator</em>).</li>
  <li><strong>windows</strong>: Máquina <strong>Windows 7</strong> conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.138</strong>. Actuará como cliente (<em>initiator</em>).</li>
</ul>

<p>La primera prueba que voy a hacer consistirá en crear un <em>target</em> que contendrá una única unidad lógica (<em>LUN</em>), que posteriormente se compartirá con la máquina cliente <strong>iscsic1</strong>.</p>

<p>Me cuento actualmente haciendo uso de la máquina servidora <strong>iscsis1</strong>, de manera que lo primero que haremos será visualizar las interfaces de red existentes en la máquina junto con sus direcciones IP asignadas, haciendo para ello uso del comando <code class="language-plaintext highlighter-rouge">ip a</code>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    <span class="nb">link</span>/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    <span class="nb">link</span>/ether 08:00:27:8d:c0:4d brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
       valid_lft 86373sec preferred_lft 86373sec
    inet6 fe80::a00:27ff:fe8d:c04d/64 scope <span class="nb">link 
       </span>valid_lft forever preferred_lft forever
3: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    <span class="nb">link</span>/ether 08:00:27:25:4d:cc brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.136/24 brd 192.168.1.255 scope global dynamic eth1
       valid_lft 86378sec preferred_lft 86378sec
    inet6 fe80::a00:27ff:fe25:4dcc/64 scope <span class="nb">link 
       </span>valid_lft forever preferred_lft forever</code></pre></figure>

<p>De las tres interfaces mostradas, la única que nos interesa es aquella de nombre <strong>eth1</strong>, que tiene un direccionamiento <strong>192.168.1.136/24</strong>, resultante de estar conectada a mi red doméstica en modo puente (<em>bridge</em>). Nos será necesario conocer dicha información para la correspondiente conexión remota.</p>

<p>De otro lado, vamos a proceder a verificar que los 3 discos se han creado y anexado correctamente a la misma, listando los dispositivos de bloques existentes en la máquina gracias al comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0 19.8G  0 disk 
├─sda1   8:1    0 18.8G  0 part /
├─sda2   8:2    0    1K  0 part 
└─sda5   8:5    0 1021M  0 part <span class="o">[</span>SWAP]
sdb      8:16   0    1G  0 disk 
sdc      8:32   0    1G  0 disk 
sdd      8:48   0    1G  0 disk</code></pre></figure>

<p>Efectivamente, se han anexado correctamente 3 discos de 1GB (<em>sdb</em>, <em>sdc</em> y <em>sdd</em>).</p>

<p>Sin embargo, como todos bien sabemos, el nombre asignado a los dispositivos de bloques no es algo totalmente identificativo a lo largo del tiempo, pues es posible que su orden o nomenclatura cambie tras un reinicio, cuando por ejemplo añadimos un nuevo dispositivo.</p>

<p>Es por ello que tendremos que identificarlos de forma inequívoca, acudiendo al directorio <strong>/dev/disk/by-id/</strong>, que nos mostrará un identificador único para cada dispositivo de bloques (no confundir con el UUID de los sistemas de fichos, ya que actualmente estamos trabajando a un nivel mucho más inferior). Para ello, vamos a listar el contenido de dicho directorio ejecutando el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# <span class="nb">ls</span> <span class="nt">-l</span> /dev/disk/by-id/
total 0
lrwxrwxrwx 1 root root  9 Feb 11 14:02 ata-VBOX_HARDDISK_VB23af71a2-40586852 -&gt; ../../sdc
lrwxrwxrwx 1 root root  9 Feb 11 14:02 ata-VBOX_HARDDISK_VBa2232259-3b0dda66 -&gt; ../../sdd
lrwxrwxrwx 1 root root  9 Feb 11 14:02 ata-VBOX_HARDDISK_VBc912936c-7d03844e -&gt; ../../sdb
lrwxrwxrwx 1 root root  9 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688 -&gt; ../../sda
lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part1 -&gt; ../../sda1
lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part2 -&gt; ../../sda2
lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part5 -&gt; ../../sda5</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, los identificadores únicos para los tres dispositivos de bloques son los siguientes:</p>

<ul>
  <li><strong>sdb</strong>: ata-VBOX_HARDDISK_VBc912936c-7d03844e</li>
  <li><strong>sdc</strong>: ata-VBOX_HARDDISK_VB23af71a2-40586852</li>
  <li><strong>sdd</strong>: ata-VBOX_HARDDISK_VBa2232259-3b0dda66</li>
</ul>

<p>Ahora que somos capaces de identificar a cada uno de los discos de forma única, todo está listo para proceder con la creación del primer <em>target</em>, que contendrá al disco <strong>sdb</strong> como <em>LUN</em>.</p>

<p>Lo primero que haremos, como es lógico, es instalar el paquete que nos permitirá actuar como servidor iSCSI, de nombre <strong>tgt</strong>, no sin antes actualizar la lista de la paquetería disponible. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>tgt</code></pre></figure>

<p>Llegados a este punto, tenemos dos posibilidades para llevar a cabo la creación del <em>target</em> en cuestión:</p>

<ul>
  <li>Generarlo mediante la línea de comandos, de manera que no perdurará tras un reinicio.</li>
  <li>Generarlo mediante ficheros de configuración, de manera que perdurará tras un reinicio.</li>
</ul>

<p>En mi caso, y conocida cuál es la finalidad del protocolo iSCSI, considero que es mucho más útil mostrar el procedimiento para generar un <em>target</em> persistente, aunque en caso de que no sean esas tus necesidades, <a href="https://gist.github.com/albertomolina/6c621aee3f80c5e7baf3c111df670cf0">aquí</a> puedes encontrar una hoja de referencia muy útil con los comandos a ejecutar para ello.</p>

<p>Para la generación persistente de dicho <em>target</em>, podemos o bien modificar el fichero de configuración principal ubicado en <strong>/etc/tgt/targets.conf</strong> o bien generar un nuevo fichero de extensión <strong>.conf</strong> dentro de <strong>/etc/tgt/conf.d/</strong>. Personalmente, recomiendo esta última opción, ya que permite llevar a cabo una gestión mucho más organizada de todos los <em>targets</em> existentes en la máquina, así que en mi caso, ejecutaré para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# nano /etc/tgt/conf.d/target1.conf</code></pre></figure>

<p>En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">&lt;target iqn.2021-02.com.alvarovf:target1&gt;
    driver iscsi
    controller_tid 1
    backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e
&lt;/target&gt;</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>target</strong>: Definimos un bloque identificado de forma única gracias al <em>IQN</em> asignado, en el que insertaremos la configuración del <em>target</em> que estamos generando. En este caso, <strong>iqn.2021-02.com.alvarovf:target1</strong>.</li>
  <li><strong>driver</strong>: Definimos el <em>driver</em> a utilizar por este <em>target</em>. En este caso, <strong>iscsi</strong>.</li>
  <li><strong>controller_tid</strong>: Asignamos un identificador numérico para el controlador. No es necesario hacerlo, ya que por defecto los asigna de manera secuencial. En este caso, <strong>1</strong>.</li>
  <li><strong>backing-store</strong>: Definimos una nueva <em>LUN</em> a exportar por el <em>target</em>, que como previamente hemos mencionado, es recomendable indicar el identificador único de dicho dispositivo de bloques (al fin y al cabo, dicho identificador es un enlace simbólico). En este caso, <strong>/dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e</strong>.</li>
</ul>

<p>Nuestro primer <em>target</em> ya ha sido definido, sin embargo, al haberlo hecho en un fichero de configuración, todavía no ha sido cargado en memoria y por tanto, no se encuentra activo. Para ello, tendremos que reiniciar el servicio para que así detecte los nuevos cambios, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# systemctl restart tgt</code></pre></figure>

<p>Una vez que el reinicio haya finalizado, podremos listar todos los <em>targets</em> actualmente existentes ejecutando para ello la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# tgtadm <span class="nt">--op</span> show <span class="nt">--mode</span> target
Target 1: iqn.2021-02.com.alvarovf:target1
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00010000
            SCSI SN: beaf10
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: null
            Backing store path: None
            Backing store flags: 
        LUN: 1
            Type: disk
            SCSI ID: IET     00010001
            SCSI SN: beaf11
            Size: 1074 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: rdwr
            Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e
            Backing store flags: 
    Account information:
    ACL information:
        ALL</code></pre></figure>

<p>Como se puede apreciar, sin entrar en demasiado detalle, se ha definido un <em>target</em> <strong>iqn.2021-02.com.alvarovf:target1</strong> que se encuentra actualmente activo (<em>ready</em>) que cuenta con un total de 2 unidades lógicas (<em>LUN</em>):</p>

<ul>
  <li><strong>LUN 0</strong>: Definida automáticamente y conocida como unidad lógica de control, que contiene las características del <em>target</em>.</li>
  <li><strong>LUN 1</strong>: Definida por nosotros, de tipo disco (<em>disk</em>) y con un tamaño total de 1074 MB, siendo la ruta del mismo la previamente indicada.</li>
</ul>

<p>Al haber definido el <em>target</em> mediante ficheros de configuración, pasa a estar disponible a través de todas las interfaces de red de forma automática (en caso de haberlo hecho desde línea de comandos, sería necesario <em>“bindearlo”</em> de forma manual).</p>

<p>Nuestra labor en la máquina servidora <strong>iscsis1</strong> ha finalizado, de manera que vamos a dar paso a la máquina cliente <strong>iscsic1</strong>, desde la que realizaremos la conexión a dicho <em>target</em>.</p>

<p>Lo primero que haremos, como es lógico, es instalar el paquete que nos permitirá actuar como cliente iSCSI, de nombre <strong>open-iscsi</strong>, no sin antes actualizar la lista de la paquetería disponible. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>open-iscsi</code></pre></figure>

<p>Dicho proceso de instalación habrá dado lugar a la generación de un nombre para el <em>initiator</em> que permitirá identificar de forma única desde el servidor a dicho cliente, así como utilizar determinados mecanismos de autenticación, como por ejemplo permitir el acceso a un determinado <em>target</em> desde un único cliente (haciendo uso como es lógico de dicho nombre).</p>

<p>Para visualizar dicho identificador del <em>initiator</em> tendremos que visualizar el contenido del fichero <strong>/etc/iscsi/initiatorname.iscsi</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# <span class="nb">cat</span> /etc/iscsi/initiatorname.iscsi
<span class="nv">InitiatorName</span><span class="o">=</span>iqn.1993-08.org.debian:01:6c92b3891490</code></pre></figure>

<p>Dicho identificador es modificable, sin embargo, no es una práctica recomendable, ya que está pensado para ser un nombre único.</p>

<p>El siguiente paso será hacer un <em>discovery</em>, que consistirá en conectarse a la máquina servidora en el puerto TCP que utiliza el protocolo por defecto (<strong>3260</strong>) y pedirle una lista de todos los <em>targets</em> disponibles, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# iscsiadm <span class="nt">--mode</span> discovery <span class="nt">--type</span> sendtargets <span class="nt">--portal</span> 192.168.1.136
192.168.1.136:3260,1 iqn.2021-02.com.alvarovf:target1</code></pre></figure>

<p><strong>Nota</strong>: Reemplácese la dirección IP del servidor por la correspondiente.</p>

<p>En este caso, como era de esperar, únicamente existe un <em>target</em> disponible, que es el que acabamos de generar, con <em>IQN</em> asociado <strong>iqn.2021-02.com.alvarovf:target1</strong>.</p>

<p>Sin embargo, lo que acabamos de hacer era simplemente para obtener información, pues todavía no nos hemos conectado a dicho <em>target</em> (<em>login</em>) para poder hacer uso de forma remota del dispositivo de bloques asociado. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# iscsiadm <span class="nt">--mode</span> node <span class="nt">-T</span> iqn.2021-02.com.alvarovf:target1 <span class="nt">--portal</span> 192.168.1.136 <span class="nt">--login</span>
Logging <span class="k">in </span>to <span class="o">[</span>iface: default, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] <span class="o">(</span>multiple<span class="o">)</span>
Login to <span class="o">[</span>iface: default, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] successful.</code></pre></figure>

<p><strong>Nota</strong>: Reemplácese el <em>IQN</em> y la dirección IP del servidor por la correspondiente.</p>

<p>En un principio, la conexión al <em>target</em> ha sido exitosa, de manera que el dispositivo de bloques debe estar ahora disponible para su uso íntegro desde la máquina cliente, de manera que vamos a listar una vez más los dispositivos de bloques existentes, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0 19.8G  0 disk 
├─sda1   8:1    0 18.8G  0 part /
├─sda2   8:2    0    1K  0 part 
└─sda5   8:5    0 1021M  0 part <span class="o">[</span>SWAP]
sdb      8:16   0    1G  0 disk</code></pre></figure>

<p>Efectivamente, un nuevo dispositivo de bloques <strong>sdb</strong> ha aparecido en la máquina, el cuál podremos utilizar según nuestras necesidades como si se tratase de un disco duro local, como por ejemplo, creando un nuevo sistema de ficheros <em>ext4</em> en su interior, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# mkfs.ext4 /dev/sdb
mke2fs 1.44.5 <span class="o">(</span>15-Dec-2018<span class="o">)</span>
Creating filesystem with 262144 4k blocks and 65536 inodes
Filesystem UUID: 0fb4df4d-5990-414f-acac-5e5c8e9b4585
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376

Allocating group tables: <span class="k">done                            
</span>Writing inode tables: <span class="k">done                            
</span>Creating journal <span class="o">(</span>8192 blocks<span class="o">)</span>: <span class="k">done
</span>Writing superblocks and filesystem accounting information: <span class="k">done</span></code></pre></figure>

<p>Una vez que el sistema de ficheros <em>ext4</em> haya sido generado, ya podremos montarlo en un directorio para empezar a hacer uso del mismo, como por ejemplo en <strong>/mnt</strong>, de manera que haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# mount /dev/sdb /mnt/</code></pre></figure>

<p>Una vez que el sistema de ficheros haya sido montado en <strong>/mnt</strong>, podremos volver a hacer uso de <code class="language-plaintext highlighter-rouge">lsblk</code> con una opción para que muestre ahora información sobre los sistemas de ficheros ubicados en los correspondientes dispositivos de bloques, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# lsblk <span class="nt">-f</span>
NAME   FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
sda                                                                     
├─sda1 ext4         983742b1-65a8-49d1-a148-a3865ea09e24   16.2G     7% /
├─sda2                                                                  
└─sda5 swap         04559374-06db-46f1-aa31-e7a4e6ec3286                <span class="o">[</span>SWAP]
sdb    ext4         0fb4df4d-5990-414f-acac-5e5c8e9b4585  906.2M     0% /mnt</code></pre></figure>

<p>Como se puede apreciar, el dispositivo de bloques <strong>sdb</strong> tiene un sistema de ficheros <em>ext4</em> en su interior, cuyo UUID es <strong>0fb4df4d-5990-414f-acac-5e5c8e9b4585</strong> (nos será necesario más adelante), además de encontrarse actualmente montado en <strong>/mnt</strong>.</p>

<p>Para hacer una pequeña prueba, vamos a generar en su interior un pequeño fichero con un contenido cualquiera, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# <span class="nb">echo</span> <span class="s2">"Hola."</span> <span class="o">&gt;</span> /mnt/prueba.txt</code></pre></figure>

<p>Para verificar que el fichero ha sido correctamente generado dentro de <strong>/mnt/</strong>, listaremos el contenido de dicho directorio ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# <span class="nb">ls</span> <span class="nt">-l</span> /mnt/
total 20
drwx------ 2 root root 16384 Feb 11 14:07 lost+found
<span class="nt">-rw-r--r--</span> 1 root root     6 Feb 11 14:08 prueba.txt</code></pre></figure>

<p>Efectivamente, así ha sido. Por último, vamos a realizar ahora la operación inversa, desconectándonos de un <em>target</em>, para así comprobar que el dispositivo de bloques deja de estar por tanto disponible. Para ello, desmontaremos antes de nada el sistema de ficheros, para no causar problemas en la integridad de los datos, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# umount /mnt</code></pre></figure>

<p>Una vez que el sistema de ficheros haya sido desmontado, podremos proceder a hacer el <em>logout</em> del <em>target</em> iSCSI, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# iscsiadm <span class="nt">--mode</span> node <span class="nt">-T</span> iqn.2021-02.com.alvarovf:target1 <span class="nt">--portal</span> 192.168.1.136 <span class="nt">-u</span>
Logging out of session <span class="o">[</span>sid: 1, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260]
Logout of <span class="o">[</span>sid: 1, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] successful.</code></pre></figure>

<p><strong>Nota</strong>: Reemplácese el <em>IQN</em> y la dirección IP del servidor por la correspondiente.</p>

<p>En un principio, la desconexión del <em>target</em> ha sido exitosa, de manera que el dispositivo de bloques debe haber desaparecido de la máquina cliente, de manera que vamos a listar una vez más los dispositivos de bloques existentes, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0 19.8G  0 disk 
├─sda1   8:1    0 18.8G  0 part /
├─sda2   8:2    0    1K  0 part 
└─sda5   8:5    0 1021M  0 part <span class="o">[</span>SWAP]</code></pre></figure>

<p>Como era de esperar, el dispositivo de bloques <strong>sdb</strong> ha desaparecido de la máquina, ya que actualmente no estamos conectados al <em>target</em> previamente generado.</p>

<p>Si lo pensamos bien, es un protocolo realmente útil y sus aplicaciones son prácticamente infinitas, sin embargo, tener que realizar manualmente el proceso de <em>login</em>  y montaje del sistema de ficheros cada vez que se inicie la máquina cliente puede llegar a ser un tanto tedioso. Es por ello que vamos a llevar a cabo la configuración necesaria para automatizar dicho proceso.</p>

<p>El primer paso consistirá en generar un directorio que actúe como punto de montaje para dicho dispositivo de bloques remoto, pues hacerlo de forma persistente dentro de <strong>/mnt</strong> no sigue las recomendaciones de la <a href="https://es.wikipedia.org/wiki/Filesystem_Hierarchy_Standard">Filesystem Hierarchy Standard</a>. En este caso, voy a generar un directorio de nombre <strong>iscsi/</strong> dentro de <strong>/mnt</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# <span class="nb">mkdir</span> /mnt/iscsi</code></pre></figure>

<p>Una vez generado el punto de montaje para dicho sistema de ficheros, daremos lugar al segundo paso, que consistirá en indicar a <strong>open-iscsi</strong> que realice el <em>login</em> a dicho <em>target</em> de forma automatizada durante el arranque de la máquina cliente, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# iscsiadm <span class="nt">--mode</span> node <span class="nt">-T</span> iqn.2021-02.com.alvarovf:target1 <span class="nt">--portal</span> 192.168.1.136 <span class="nt">-o</span> update <span class="nt">-n</span> node.startup <span class="nt">-v</span> automatic</code></pre></figure>

<p><strong>Nota</strong>: Reemplácese el <em>IQN</em> y la dirección IP del servidor por la correspondiente.</p>

<p>Por último, tendremos que crear una unidad <strong>systemd</strong> para gestionar el montaje automático de dicho sistema de ficheros (aunque también podríamos hacerlo en el <strong>/etc/fstab</strong>), que deberá ubicarse dentro de <strong>/etc/systemd/system/</strong> y su nombre deberá ser <strong>[puntomontaje].mount</strong>, sustituyendo las <strong>/</strong> de la ruta por <strong>-</strong>, quedando en mi caso de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# nano /etc/systemd/system/mnt-iscsi.mount</code></pre></figure>

<p>En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Prueba iSCSI    

<span class="o">[</span>Mount]
<span class="nv">What</span><span class="o">=</span>/dev/disk/by-uuid/0fb4df4d-5990-414f-acac-5e5c8e9b4585
<span class="nv">Where</span><span class="o">=</span>/mnt/iscsi   
<span class="nv">Type</span><span class="o">=</span>ext4
<span class="nv">Options</span><span class="o">=</span>_netdev

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>multi-user.target</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>Description</strong>: Establecemos una descripción para la unidad <strong>systemd</strong>, que no es algo relevante.</li>
  <li><strong>What</strong>: Indicamos qué es lo que queremos montar. En este caso, será el sistema de ficheros con el UUID previamente obtenido, pues hacer referencia al mismo mediante el nombre de dispositivo no es algo identificativo a largo plazo.</li>
  <li><strong>Where</strong>: Indicamos el punto de montaje para el sistema de ficheros en cuestión, que en este caso será el directorio que acabamos de generar.</li>
  <li><strong>Type</strong>: Indicamos el tipo de sistema de ficheros que deseamos montar.</li>
  <li><strong>Options</strong>: Indicamos las opciones de montaje para dicho sistema de ficheros, en este caso, es muy importante poner <strong>_netdev</strong> para que no intente montar sistemas de ficheros compartidos por red hasta que ésta no esté disponible y por tanto no ralentice el proceso de arranque.</li>
  <li><strong>WantedBy</strong>: Indicamos las dependencias de la unidad <strong>systemd</strong>, que en este caso, indica que será necesario que todos los servicios de red hayan arrancado y que el sistema acepte <em>logins</em> por parte de los usuarios, pero no es necesario que la <em>GUI</em> haya sido iniciada.</li>
</ul>

<p>Una vez finalizada la creación de la unidad, guardaremos los cambios y saldremos para así habilitar la misma para que arranque de forma automática en el siguiente inicio de la máquina, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# systemctl <span class="nb">enable </span>mnt-iscsi.mount</code></pre></figure>

<p>Ha llegado el momento de <em>la prueba de fuego</em>, de manera que vamos a proceder a reiniciar la máquina ejecutando para ello el comando <code class="language-plaintext highlighter-rouge">reboot</code> para así poder verificar que el funcionamiento de dicha unidad es el esperado.</p>

<p>Lo primero que haremos será comprobar el estado de la unidad <strong>systemd</strong> de nombre <strong>mnt-iscsi.mount</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# systemctl status mnt-iscsi.mount
● mnt-iscsi.mount - Prueba iSCSI
   Loaded: loaded <span class="o">(</span>/etc/systemd/system/mnt-iscsi.mount<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>mounted<span class="o">)</span> since Thu 2021-02-11 14:12:47 GMT<span class="p">;</span> 20s ago
    Where: /mnt/iscsi
     What: /dev/sdb
    Tasks: 0 <span class="o">(</span>limit: 544<span class="o">)</span>
   Memory: 120.0K
   CGroup: /system.slice/mnt-iscsi.mount</code></pre></figure>

<p>Como era de esperar, la unidad se encuentra actualmente activa como consecuencia de haber habilitado su arranque durante el inicio de la máquina, de manera que el sistema de ficheros debería haber sido correctamente montado de forma automática en <strong>/mnt/iscsi</strong>, por lo que vamos a comprobarlo listando una vez más los dispositivos de bloques existentes en la máquina cliente, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0 19.8G  0 disk 
├─sda1   8:1    0 18.8G  0 part /
├─sda2   8:2    0    1K  0 part 
└─sda5   8:5    0 1021M  0 part <span class="o">[</span>SWAP]
sdb      8:16   0    1G  0 disk /mnt/iscsi</code></pre></figure>

<p>Efectivamente, se ha hecho un <em>login</em> de forma automática en el <em>target</em> previamente generado en la máquina servidora, además de haberse montado el sistema de ficheros del dispositivo de bloques compartido en el punto de montaje especificado.</p>

<p>De igual forma, vamos a listar el contenido de dicho directorio para verificar que el fichero previamente creado sigue existiendo en el mismo, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsic1:~# <span class="nb">ls</span> <span class="nt">-l</span> /mnt/iscsi/
total 20
drwx------ 2 root root 16384 Feb 11 14:07 lost+found
<span class="nt">-rw-r--r--</span> 1 root root     6 Feb 11 14:08 prueba.txt</code></pre></figure>

<p>Como se puede apreciar, el contenido sigue siendo exactamente el mismo, por lo que podemos concluir que toda la configuración llevada a cabo hasta ahora ha funcionado tal y como debería.</p>

<p>La segunda prueba que voy a hacer consistirá en crear un nuevo <em>target</em> que contendrá dos unidades lógicas (<em>LUN</em>), que posteriormente se compartirá con la máquina cliente <strong>windows</strong> pero utilizando la autenticación CHAP que nos proporciona el protocolo iSCSI.</p>

<p>Me cuento actualmente haciendo uso de la máquina servidora <strong>iscsis1</strong>, de manera que todo está listo para proceder con la creación del segundo <em>target</em>, que contendrá a los discos <strong>sdc</strong> y <strong>sdd</strong> como <em>LUN</em>.</p>

<p>Lo primero que haremos, como ya sabemos, es generar un nuevo fichero de extensión <strong>.conf</strong> dentro de <strong>/etc/tgt/conf.d/</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# nano /etc/tgt/conf.d/target2.conf</code></pre></figure>

<p>En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">&lt;target iqn.2021-02.com.alvarovf:target2&gt;
    driver iscsi
    controller_tid 2
    backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VB23af71a2-40586852
    backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VBa2232259-3b0dda66
    incominguser alvaro pruebaiscsi2021
&lt;/target&gt;</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>incominguser</strong>: Definimos un usuario y una contraseña que tendrá permisos para hacer uso del <em>target</em> (conocido como autenticación <strong>CHAP</strong>). En este caso, existirá un usuario <strong>alvaro</strong> con contraseña <strong>pruebaiscsi2021</strong> que podrá utilizar dicho <em>target</em>.</li>
</ul>

<p>Nuestro segundo <em>target</em> ya ha sido definido, sin embargo, al haberlo hecho en un fichero de configuración, todavía no ha sido cargado en memoria y por tanto, no se encuentra activo. Para ello, tendremos que reiniciar el servicio para que así detecte los nuevos cambios, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# systemctl restart tgt</code></pre></figure>

<p>Una vez que el reinicio haya finalizado, podremos listar todos los <em>targets</em> actualmente existentes ejecutando para ello la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@iscsis1:~# tgtadm <span class="nt">--lld</span> iscsi <span class="nt">--op</span> show <span class="nt">--mode</span> target
Target 1: iqn.2021-02.com.alvarovf:target1
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
        I_T nexus: 1
            Initiator: iqn.1993-08.org.debian:01:6c92b3891490 <span class="nb">alias</span>: iscsic1
            Connection: 0
                IP Address: 192.168.1.137
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00010000
            SCSI SN: beaf10
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: null
            Backing store path: None
            Backing store flags: 
        LUN: 1
            Type: disk
            SCSI ID: IET     00010001
            SCSI SN: beaf11
            Size: 1074 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: rdwr
            Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e
            Backing store flags: 
    Account information:
    ACL information:
        ALL
Target 2: iqn.2021-02.com.alvarovf:target2
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00020000
            SCSI SN: beaf20
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: null
            Backing store path: None
            Backing store flags: 
        LUN: 1
            Type: disk
            SCSI ID: IET     00020001
            SCSI SN: beaf21
            Size: 1074 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: rdwr
            Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VB23af71a2-40586852
            Backing store flags: 
        LUN: 2
            Type: disk
            SCSI ID: IET     00020002
            SCSI SN: beaf22
            Size: 1074 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store <span class="nb">type</span>: rdwr
            Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBa2232259-3b0dda66
            Backing store flags: 
    Account information:
        alvaro
    ACL information:
        ALL</code></pre></figure>

<p>Como se puede apreciar, sin entrar en demasiado detalle, se ha definido un segundo <em>target</em> <strong>iqn.2021-02.com.alvarovf:target2</strong> que se encuentra actualmente activo (<em>ready</em>) que cuenta con un total de 3 unidades lógicas (<em>LUN</em>):</p>

<ul>
  <li><strong>LUN 0</strong>: Definida automáticamente y conocida como unidad lógica de control, que contiene las características del <em>target</em>.</li>
  <li><strong>LUN 1</strong>: Definida por nosotros, de tipo disco (<em>disk</em>) y con un tamaño total de 1074 MB, siendo la ruta del mismo el identificador único del disco <strong>sdc</strong>.</li>
  <li><strong>LUN 2</strong>: Definida por nosotros, de tipo disco (<em>disk</em>) y con un tamaño total de 1074 MB, siendo la ruta del mismo el identificador único del disco <strong>sdd</strong>.</li>
</ul>

<p>Nuestra labor en la máquina servidora <strong>iscsis1</strong> ha finalizado, de manera que vamos a dar paso a la máquina cliente <strong>windows</strong>, desde la que realizaremos la conexión a dicho <em>target</em>.</p>

<p>Lo primero que haremos, como es lógico, es acceder a la aplicación que nos permitirá actuar como cliente iSCSI, de nombre <strong>iSCSI Initiator</strong>. Para ello, haremos una búsqueda de la misma mediante el explorador:</p>

<p><img src="https://i.ibb.co/m43rqmw/Captura-de-pantalla-de-2021-02-09-17-46-38.png" alt="windows1" title="iSCSI Windows" /></p>

<p>Cuando nos encontremos dentro de la misma, tendremos que hacer un <em>discovery</em>, que consistirá en conectarse a la máquina servidora en el puerto TCP que utiliza el protocolo por defecto (<strong>3260</strong>) y pedirle una lista de todos los <em>targets</em> disponibles, accediendo para ello al apartado <strong>Discovery</strong> en el menú superior:</p>

<p><img src="https://i.ibb.co/dfQRKSp/Captura-de-pantalla-de-2021-02-09-18-00-15.png" alt="windows2" title="iSCSI Windows" /></p>

<p>Dentro de dicho apartado, tendremos que pulsar en <strong>Discover Portal…</strong> para así añadir un nuevo servidor (<em>portal</em>) destino, que nos abrirá una ventana de la siguiente forma:</p>

<p><img src="https://i.ibb.co/3rtHgjh/Captura-de-pantalla-de-2021-02-09-18-00-34.png" alt="windows3" title="iSCSI Windows" /></p>

<p>Como se puede suponer, tendremos que indicar la dirección IP de la máquina servidora <strong>iscsis1</strong>, así como el puerto en el que está escuchando dichas peticiones, que es el utilizado por defecto. Si tras ello volvemos al apartado <strong>Targets</strong>, podremos apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/ww5Hwd7/Captura-de-pantalla-de-2021-02-09-18-02-02.png" alt="windows4" title="iSCSI Windows" /></p>

<p>En este caso, como era de esperar, existen un total de dos <em>targets</em> disponibles, que son aquellos que manualmente hemos generado, con <em>IQN</em> asociados <strong>iqn.2021-02.com.alvarovf:target1</strong> y <strong>iqn.2021-02.com.alvarovf:target2</strong>, respectivamente.</p>

<p>Sin embargo, lo que acabamos de hacer era simplemente para obtener información, pues todavía no nos hemos conectado al segundo <em>target</em> (<em>login</em>) para poder hacer uso de forma remota de los dispositivos de bloques asociados. Para ello, lo seleccionaremos y pulsaremos en <strong>Connect</strong>:</p>

<p><img src="https://i.ibb.co/mB9nZz8/Captura-de-pantalla-de-2021-02-09-18-03-45.png" alt="windows5" title="iSCSI Windows" /></p>

<p>Dado que estamos haciendo uso de una autenticación CHAP, no bastará con pulsar en <strong>OK</strong> para realizar la conexión, sino que tendremos que introducir las credenciales del usuario con permisos para ello.</p>

<p>Simplemente, tendremos que pulsar en <strong>Advanced</strong> y se nos abrirá una nueva ventana de la siguiente forma:</p>

<p><img src="https://i.ibb.co/VN97DJ8/Captura-de-pantalla-de-2021-02-09-18-08-31.png" alt="windows6" title="iSCSI Windows" /></p>

<p>Como se puede apreciar, hemos marcado la casilla <strong>Enable CHAP log on</strong> y hemos especificado las siguientes credenciales en el mismo, que tendrán que coincidir con las asignadas en el momento de la creación del <em>target</em>:</p>

<ul>
  <li><strong>Name</strong>: alvaro</li>
  <li><strong>Target secret</strong>: pruebaiscsi2021</li>
</ul>

<p>Tras ello, pulsaremos en <strong>OK</strong> para realizar la conexión y podremos apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/pZxqJ54/Captura-de-pantalla-de-2021-02-09-18-08-43.png" alt="windows7" title="iSCSI Windows" /></p>

<p>En un principio, la conexión al segundo <em>target</em> ha sido exitosa, de manera que los dos dispositivos de bloques deben estar ahora disponibles para su uso íntegro desde la máquina cliente <strong>windows</strong>. Para verificarlo, accederemos a <strong>Create and format hard disk partitions</strong>, haciendo para ello una búsqueda mediante el explorador, en caso de ser necesario:</p>

<p><img src="https://i.ibb.co/R4FQMZy/Captura-de-pantalla-de-2021-02-09-18-08-59.png" alt="windows8" title="iSCSI Windows" /></p>

<p>Cuando hayamos accedido, nos aparecerá una ventana emergente indicando que se han detectado dos nuevos discos que no se encuentran actualmente inicializados ni tampoco cuentan con tabla de particiones, de manera que la crearemos en su interior pulsando para ello en <strong>OK</strong>:</p>

<p><img src="https://i.ibb.co/19p8CRj/Captura-de-pantalla-de-2021-02-09-18-09-28.png" alt="windows9" title="iSCSI Windows" /></p>

<p>Los discos ya habrán sido iniciados y tendrán una tabla de particiones en su interior, de manera que simplemente queda alojar un sistema de ficheros <em>NTFS</em> en los mismos. Para ello, haremos click derecho sobre el primero de ellos y pulsaremos en <strong>New Simple Volume…</strong>:</p>

<p><img src="https://i.ibb.co/v1S0sWB/Captura-de-pantalla-de-2021-02-09-18-09-49.png" alt="windows10" title="iSCSI Windows" /></p>

<p>Tras ello se abrirá una nueva ventana emergente que nos permitirá continuar con el proceso, consistente como suele ser común en Windows, en pulsar en <strong>Siguiente</strong> en reiteradas ocasiones. Repetiremos el mismo procedimiento para el segundo disco, de manera que el resultado final debe ser similar al siguiente:</p>

<p><img src="https://i.ibb.co/QCthWPc/Captura-de-pantalla-de-2021-02-09-18-11-11.png" alt="windows11" title="iSCSI Windows" /></p>

<p>Efectivamente, los discos ya cuentan con un sistema de ficheros <em>NTFS</em> en su interior, por lo que estarán totalmente operativos y listos para su uso. Para verificarlo de nuevo, accederemos al explorador de Windows y podremos apreciar que las dos nuevas unidades aparecen ahí y podríamos empezar a hacer uso de las mismas desde este momento:</p>

<p><img src="https://i.ibb.co/jrvnjCW/Captura-de-pantalla-de-2021-02-09-18-11-31.png" alt="windows12" title="iSCSI Windows" /></p>

<p>A mi parecer, el artículo ha sido bastante claro y conciso, mencionando en todo momento las numerosas ventajas que nos ofrece este simple pero útil protocolo para compartir dispositivos de bloques a través de la red, de manera que ya queda ser creativo y aplicarlo en aquellos escenarios en los que pueda ofrecernos ventajas. ¡Un saludo!</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="hlc" /><summary type="html"><![CDATA[El protocolo iSCSI es un protocolo que nos proporciona acceso a dispositivos de bloques sobre la red (TCP/IP). A diferencia de otros protocolos como NFS o Samba, no proporciona ficheros o directorios, sino dispositivos de bloques de forma íntegra, es decir, se conecta un nuevo disco duro y es posible compartir el disco duro en bruto a través de la red, habilitando su uso de forma remota, sin necesidad de crear una tabla de particiones ni introducir un sistema de ficheros en el mismo, pues esa será labor del extremo que actúe como cliente, que tendrá control total sobre dicho dispositivo de bloques. Otra de las grandes características es que nos permite montar el mismo dispositivo de bloques en varios equipos clientes de forma simultánea (puede llegar a provocar un problema de concurrencia que habría que solventar a otro nivel, pero como tal, está soportado), de una forma mucho más económica que Fibre Channel, teniendo en cuenta además que tiene soporte en la mayoría de sistemas operativos. Dicho protocolo suele utilizarse en redes de almacenamiento, ya que nos proporcionan un aislamiento adecuado para compartir dichos dispositivos de una forma más segura, aunque tal y como veremos a continuación, dicho protocolo nos ofrece sus propios mecanismos de autenticación. Antes de empezar a hacer uso de forma práctica de dicho protocolo, es necesario conocer una serie de términos: Unidad lógica (LUN): Dispositivo de bloques a compartir por el servidor iSCSI (discos duros, particiones, volúmenes lógicos…). Target: Recurso a compartir desde el servidor, que incluye una o varias LUN. Por ejemplo, podemos conectar a la máquina servidora 3 discos duros e incorporarlos dentro de un mismo target, al que posteriormente se conectará un cliente mediante una única conexión y podrá hacer uso de dichos discos. Dependiendo del caso, también podrían haberse creado 3 targets, uno por cada disco, y distribuirlos de distinta manera. Initiator: Cliente iSCSI que realiza la conexión. Multipath: Permite garantizar la disponibilidad del dispositivo de bloques remoto en caso de haber más de una ruta posible entre el target y el initiator, pues en caso de perder la conexión mediante una ruta, dicha conexión se mantendría a través de otra. Como es lógico, depende de la infraestructura que tengamos. IQN: Formato utilizado para la descripción única de los recursos que se comparten. Se suele utilizar una regla para nombrarlos de la forma iqn.(año)-(mes).(nombre de dominio invertido):(nombre único). Por ejemplo, iqn.2021-02.com.alvarovf:target1. iSNS: Protocolo que permite gestionar recursos iSCSI como si fueran Fibre Channel. Una vez comprendido qué es el protocolo iSCSI y su funcionamiento de forma superficial, vamos a proceder a aclarar dichos conceptos de una forma más práctica. Para ello, he generado un pequeño escenario compuesto por las siguientes máquinas: iscsis1: Máquina Debian Buster conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.136. Actuará como servidor y tiene 3 discos duros de 1GB asociados. iscsic1: Máquina Debian Buster conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.137. Actuará como cliente (initiator). windows: Máquina Windows 7 conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.138. Actuará como cliente (initiator). La primera prueba que voy a hacer consistirá en crear un target que contendrá una única unidad lógica (LUN), que posteriormente se compartirá con la máquina cliente iscsic1. Me cuento actualmente haciendo uso de la máquina servidora iscsis1, de manera que lo primero que haremos será visualizar las interfaces de red existentes en la máquina junto con sus direcciones IP asignadas, haciendo para ello uso del comando ip a: root@iscsis1:~# ip a 1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:8d:c0:4d brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0 valid_lft 86373sec preferred_lft 86373sec inet6 fe80::a00:27ff:fe8d:c04d/64 scope link valid_lft forever preferred_lft forever 3: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:25:4d:cc brd ff:ff:ff:ff:ff:ff inet 192.168.1.136/24 brd 192.168.1.255 scope global dynamic eth1 valid_lft 86378sec preferred_lft 86378sec inet6 fe80::a00:27ff:fe25:4dcc/64 scope link valid_lft forever preferred_lft forever De las tres interfaces mostradas, la única que nos interesa es aquella de nombre eth1, que tiene un direccionamiento 192.168.1.136/24, resultante de estar conectada a mi red doméstica en modo puente (bridge). Nos será necesario conocer dicha información para la correspondiente conexión remota. De otro lado, vamos a proceder a verificar que los 3 discos se han creado y anexado correctamente a la misma, listando los dispositivos de bloques existentes en la máquina gracias al comando: root@iscsis1:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 19.8G 0 disk ├─sda1 8:1 0 18.8G 0 part / ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 1021M 0 part [SWAP] sdb 8:16 0 1G 0 disk sdc 8:32 0 1G 0 disk sdd 8:48 0 1G 0 disk Efectivamente, se han anexado correctamente 3 discos de 1GB (sdb, sdc y sdd). Sin embargo, como todos bien sabemos, el nombre asignado a los dispositivos de bloques no es algo totalmente identificativo a lo largo del tiempo, pues es posible que su orden o nomenclatura cambie tras un reinicio, cuando por ejemplo añadimos un nuevo dispositivo. Es por ello que tendremos que identificarlos de forma inequívoca, acudiendo al directorio /dev/disk/by-id/, que nos mostrará un identificador único para cada dispositivo de bloques (no confundir con el UUID de los sistemas de fichos, ya que actualmente estamos trabajando a un nivel mucho más inferior). Para ello, vamos a listar el contenido de dicho directorio ejecutando el comando: root@iscsis1:~# ls -l /dev/disk/by-id/ total 0 lrwxrwxrwx 1 root root 9 Feb 11 14:02 ata-VBOX_HARDDISK_VB23af71a2-40586852 -&gt; ../../sdc lrwxrwxrwx 1 root root 9 Feb 11 14:02 ata-VBOX_HARDDISK_VBa2232259-3b0dda66 -&gt; ../../sdd lrwxrwxrwx 1 root root 9 Feb 11 14:02 ata-VBOX_HARDDISK_VBc912936c-7d03844e -&gt; ../../sdb lrwxrwxrwx 1 root root 9 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688 -&gt; ../../sda lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part1 -&gt; ../../sda1 lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part2 -&gt; ../../sda2 lrwxrwxrwx 1 root root 10 Feb 11 14:02 ata-VBOX_HARDDISK_VBce9c25fa-bef74688-part5 -&gt; ../../sda5 Como se puede apreciar en la salida del comando ejecutado, los identificadores únicos para los tres dispositivos de bloques son los siguientes: sdb: ata-VBOX_HARDDISK_VBc912936c-7d03844e sdc: ata-VBOX_HARDDISK_VB23af71a2-40586852 sdd: ata-VBOX_HARDDISK_VBa2232259-3b0dda66 Ahora que somos capaces de identificar a cada uno de los discos de forma única, todo está listo para proceder con la creación del primer target, que contendrá al disco sdb como LUN. Lo primero que haremos, como es lógico, es instalar el paquete que nos permitirá actuar como servidor iSCSI, de nombre tgt, no sin antes actualizar la lista de la paquetería disponible. Para ello, haremos uso del comando: root@iscsis1:~# apt update &amp;&amp; apt install tgt Llegados a este punto, tenemos dos posibilidades para llevar a cabo la creación del target en cuestión: Generarlo mediante la línea de comandos, de manera que no perdurará tras un reinicio. Generarlo mediante ficheros de configuración, de manera que perdurará tras un reinicio. En mi caso, y conocida cuál es la finalidad del protocolo iSCSI, considero que es mucho más útil mostrar el procedimiento para generar un target persistente, aunque en caso de que no sean esas tus necesidades, aquí puedes encontrar una hoja de referencia muy útil con los comandos a ejecutar para ello. Para la generación persistente de dicho target, podemos o bien modificar el fichero de configuración principal ubicado en /etc/tgt/targets.conf o bien generar un nuevo fichero de extensión .conf dentro de /etc/tgt/conf.d/. Personalmente, recomiendo esta última opción, ya que permite llevar a cabo una gestión mucho más organizada de todos los targets existentes en la máquina, así que en mi caso, ejecutaré para ello el comando: root@iscsis1:~# nano /etc/tgt/conf.d/target1.conf En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente: &lt;target iqn.2021-02.com.alvarovf:target1&gt; driver iscsi controller_tid 1 backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e &lt;/target&gt; Donde: target: Definimos un bloque identificado de forma única gracias al IQN asignado, en el que insertaremos la configuración del target que estamos generando. En este caso, iqn.2021-02.com.alvarovf:target1. driver: Definimos el driver a utilizar por este target. En este caso, iscsi. controller_tid: Asignamos un identificador numérico para el controlador. No es necesario hacerlo, ya que por defecto los asigna de manera secuencial. En este caso, 1. backing-store: Definimos una nueva LUN a exportar por el target, que como previamente hemos mencionado, es recomendable indicar el identificador único de dicho dispositivo de bloques (al fin y al cabo, dicho identificador es un enlace simbólico). En este caso, /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e. Nuestro primer target ya ha sido definido, sin embargo, al haberlo hecho en un fichero de configuración, todavía no ha sido cargado en memoria y por tanto, no se encuentra activo. Para ello, tendremos que reiniciar el servicio para que así detecte los nuevos cambios, haciendo para ello uso del comando: root@iscsis1:~# systemctl restart tgt Una vez que el reinicio haya finalizado, podremos listar todos los targets actualmente existentes ejecutando para ello la instrucción: root@iscsis1:~# tgtadm --op show --mode target Target 1: iqn.2021-02.com.alvarovf:target1 System information: Driver: iscsi State: ready I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: IET 00010000 SCSI SN: beaf10 Size: 0 MB, Block size: 1 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: null Backing store path: None Backing store flags: LUN: 1 Type: disk SCSI ID: IET 00010001 SCSI SN: beaf11 Size: 1074 MB, Block size: 512 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: rdwr Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e Backing store flags: Account information: ACL information: ALL Como se puede apreciar, sin entrar en demasiado detalle, se ha definido un target iqn.2021-02.com.alvarovf:target1 que se encuentra actualmente activo (ready) que cuenta con un total de 2 unidades lógicas (LUN): LUN 0: Definida automáticamente y conocida como unidad lógica de control, que contiene las características del target. LUN 1: Definida por nosotros, de tipo disco (disk) y con un tamaño total de 1074 MB, siendo la ruta del mismo la previamente indicada. Al haber definido el target mediante ficheros de configuración, pasa a estar disponible a través de todas las interfaces de red de forma automática (en caso de haberlo hecho desde línea de comandos, sería necesario “bindearlo” de forma manual). Nuestra labor en la máquina servidora iscsis1 ha finalizado, de manera que vamos a dar paso a la máquina cliente iscsic1, desde la que realizaremos la conexión a dicho target. Lo primero que haremos, como es lógico, es instalar el paquete que nos permitirá actuar como cliente iSCSI, de nombre open-iscsi, no sin antes actualizar la lista de la paquetería disponible. Para ello, haremos uso del comando: root@iscsic1:~# apt update &amp;&amp; apt install open-iscsi Dicho proceso de instalación habrá dado lugar a la generación de un nombre para el initiator que permitirá identificar de forma única desde el servidor a dicho cliente, así como utilizar determinados mecanismos de autenticación, como por ejemplo permitir el acceso a un determinado target desde un único cliente (haciendo uso como es lógico de dicho nombre). Para visualizar dicho identificador del initiator tendremos que visualizar el contenido del fichero /etc/iscsi/initiatorname.iscsi, ejecutando para ello el comando: root@iscsic1:~# cat /etc/iscsi/initiatorname.iscsi InitiatorName=iqn.1993-08.org.debian:01:6c92b3891490 Dicho identificador es modificable, sin embargo, no es una práctica recomendable, ya que está pensado para ser un nombre único. El siguiente paso será hacer un discovery, que consistirá en conectarse a la máquina servidora en el puerto TCP que utiliza el protocolo por defecto (3260) y pedirle una lista de todos los targets disponibles, haciendo para ello uso del comando: root@iscsic1:~# iscsiadm --mode discovery --type sendtargets --portal 192.168.1.136 192.168.1.136:3260,1 iqn.2021-02.com.alvarovf:target1 Nota: Reemplácese la dirección IP del servidor por la correspondiente. En este caso, como era de esperar, únicamente existe un target disponible, que es el que acabamos de generar, con IQN asociado iqn.2021-02.com.alvarovf:target1. Sin embargo, lo que acabamos de hacer era simplemente para obtener información, pues todavía no nos hemos conectado a dicho target (login) para poder hacer uso de forma remota del dispositivo de bloques asociado. Para ello, ejecutaremos el comando: root@iscsic1:~# iscsiadm --mode node -T iqn.2021-02.com.alvarovf:target1 --portal 192.168.1.136 --login Logging in to [iface: default, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] (multiple) Login to [iface: default, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] successful. Nota: Reemplácese el IQN y la dirección IP del servidor por la correspondiente. En un principio, la conexión al target ha sido exitosa, de manera que el dispositivo de bloques debe estar ahora disponible para su uso íntegro desde la máquina cliente, de manera que vamos a listar una vez más los dispositivos de bloques existentes, haciendo para ello uso del comando: root@iscsic1:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 19.8G 0 disk ├─sda1 8:1 0 18.8G 0 part / ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 1021M 0 part [SWAP] sdb 8:16 0 1G 0 disk Efectivamente, un nuevo dispositivo de bloques sdb ha aparecido en la máquina, el cuál podremos utilizar según nuestras necesidades como si se tratase de un disco duro local, como por ejemplo, creando un nuevo sistema de ficheros ext4 en su interior, ejecutando para ello el comando: root@iscsic1:~# mkfs.ext4 /dev/sdb mke2fs 1.44.5 (15-Dec-2018) Creating filesystem with 262144 4k blocks and 65536 inodes Filesystem UUID: 0fb4df4d-5990-414f-acac-5e5c8e9b4585 Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done Una vez que el sistema de ficheros ext4 haya sido generado, ya podremos montarlo en un directorio para empezar a hacer uso del mismo, como por ejemplo en /mnt, de manera que haremos uso del comando: root@iscsic1:~# mount /dev/sdb /mnt/ Una vez que el sistema de ficheros haya sido montado en /mnt, podremos volver a hacer uso de lsblk con una opción para que muestre ahora información sobre los sistemas de ficheros ubicados en los correspondientes dispositivos de bloques, ejecutando para ello el comando: root@iscsic1:~# lsblk -f NAME FSTYPE LABEL UUID FSAVAIL FSUSE% MOUNTPOINT sda ├─sda1 ext4 983742b1-65a8-49d1-a148-a3865ea09e24 16.2G 7% / ├─sda2 └─sda5 swap 04559374-06db-46f1-aa31-e7a4e6ec3286 [SWAP] sdb ext4 0fb4df4d-5990-414f-acac-5e5c8e9b4585 906.2M 0% /mnt Como se puede apreciar, el dispositivo de bloques sdb tiene un sistema de ficheros ext4 en su interior, cuyo UUID es 0fb4df4d-5990-414f-acac-5e5c8e9b4585 (nos será necesario más adelante), además de encontrarse actualmente montado en /mnt. Para hacer una pequeña prueba, vamos a generar en su interior un pequeño fichero con un contenido cualquiera, haciendo para ello uso del comando: root@iscsic1:~# echo "Hola." &gt; /mnt/prueba.txt Para verificar que el fichero ha sido correctamente generado dentro de /mnt/, listaremos el contenido de dicho directorio ejecutando para ello el comando: root@iscsic1:~# ls -l /mnt/ total 20 drwx------ 2 root root 16384 Feb 11 14:07 lost+found -rw-r--r-- 1 root root 6 Feb 11 14:08 prueba.txt Efectivamente, así ha sido. Por último, vamos a realizar ahora la operación inversa, desconectándonos de un target, para así comprobar que el dispositivo de bloques deja de estar por tanto disponible. Para ello, desmontaremos antes de nada el sistema de ficheros, para no causar problemas en la integridad de los datos, haciendo para ello uso del comando: root@iscsic1:~# umount /mnt Una vez que el sistema de ficheros haya sido desmontado, podremos proceder a hacer el logout del target iSCSI, ejecutando para ello el comando: root@iscsic1:~# iscsiadm --mode node -T iqn.2021-02.com.alvarovf:target1 --portal 192.168.1.136 -u Logging out of session [sid: 1, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] Logout of [sid: 1, target: iqn.2021-02.com.alvarovf:target1, portal: 192.168.1.136,3260] successful. Nota: Reemplácese el IQN y la dirección IP del servidor por la correspondiente. En un principio, la desconexión del target ha sido exitosa, de manera que el dispositivo de bloques debe haber desaparecido de la máquina cliente, de manera que vamos a listar una vez más los dispositivos de bloques existentes, haciendo para ello uso del comando: root@iscsic1:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 19.8G 0 disk ├─sda1 8:1 0 18.8G 0 part / ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 1021M 0 part [SWAP] Como era de esperar, el dispositivo de bloques sdb ha desaparecido de la máquina, ya que actualmente no estamos conectados al target previamente generado. Si lo pensamos bien, es un protocolo realmente útil y sus aplicaciones son prácticamente infinitas, sin embargo, tener que realizar manualmente el proceso de login y montaje del sistema de ficheros cada vez que se inicie la máquina cliente puede llegar a ser un tanto tedioso. Es por ello que vamos a llevar a cabo la configuración necesaria para automatizar dicho proceso. El primer paso consistirá en generar un directorio que actúe como punto de montaje para dicho dispositivo de bloques remoto, pues hacerlo de forma persistente dentro de /mnt no sigue las recomendaciones de la Filesystem Hierarchy Standard. En este caso, voy a generar un directorio de nombre iscsi/ dentro de /mnt, ejecutando para ello el comando: root@iscsic1:~# mkdir /mnt/iscsi Una vez generado el punto de montaje para dicho sistema de ficheros, daremos lugar al segundo paso, que consistirá en indicar a open-iscsi que realice el login a dicho target de forma automatizada durante el arranque de la máquina cliente, ejecutando para ello el comando: root@iscsic1:~# iscsiadm --mode node -T iqn.2021-02.com.alvarovf:target1 --portal 192.168.1.136 -o update -n node.startup -v automatic Nota: Reemplácese el IQN y la dirección IP del servidor por la correspondiente. Por último, tendremos que crear una unidad systemd para gestionar el montaje automático de dicho sistema de ficheros (aunque también podríamos hacerlo en el /etc/fstab), que deberá ubicarse dentro de /etc/systemd/system/ y su nombre deberá ser [puntomontaje].mount, sustituyendo las / de la ruta por -, quedando en mi caso de la siguiente forma: root@iscsic1:~# nano /etc/systemd/system/mnt-iscsi.mount En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente: [Unit] Description=Prueba iSCSI [Mount] What=/dev/disk/by-uuid/0fb4df4d-5990-414f-acac-5e5c8e9b4585 Where=/mnt/iscsi Type=ext4 Options=_netdev [Install] WantedBy=multi-user.target Donde: Description: Establecemos una descripción para la unidad systemd, que no es algo relevante. What: Indicamos qué es lo que queremos montar. En este caso, será el sistema de ficheros con el UUID previamente obtenido, pues hacer referencia al mismo mediante el nombre de dispositivo no es algo identificativo a largo plazo. Where: Indicamos el punto de montaje para el sistema de ficheros en cuestión, que en este caso será el directorio que acabamos de generar. Type: Indicamos el tipo de sistema de ficheros que deseamos montar. Options: Indicamos las opciones de montaje para dicho sistema de ficheros, en este caso, es muy importante poner _netdev para que no intente montar sistemas de ficheros compartidos por red hasta que ésta no esté disponible y por tanto no ralentice el proceso de arranque. WantedBy: Indicamos las dependencias de la unidad systemd, que en este caso, indica que será necesario que todos los servicios de red hayan arrancado y que el sistema acepte logins por parte de los usuarios, pero no es necesario que la GUI haya sido iniciada. Una vez finalizada la creación de la unidad, guardaremos los cambios y saldremos para así habilitar la misma para que arranque de forma automática en el siguiente inicio de la máquina, haciendo para ello uso del comando: root@iscsic1:~# systemctl enable mnt-iscsi.mount Ha llegado el momento de la prueba de fuego, de manera que vamos a proceder a reiniciar la máquina ejecutando para ello el comando reboot para así poder verificar que el funcionamiento de dicha unidad es el esperado. Lo primero que haremos será comprobar el estado de la unidad systemd de nombre mnt-iscsi.mount, ejecutando para ello el comando: root@iscsic1:~# systemctl status mnt-iscsi.mount ● mnt-iscsi.mount - Prueba iSCSI Loaded: loaded (/etc/systemd/system/mnt-iscsi.mount; enabled; vendor preset: enabled) Active: active (mounted) since Thu 2021-02-11 14:12:47 GMT; 20s ago Where: /mnt/iscsi What: /dev/sdb Tasks: 0 (limit: 544) Memory: 120.0K CGroup: /system.slice/mnt-iscsi.mount Como era de esperar, la unidad se encuentra actualmente activa como consecuencia de haber habilitado su arranque durante el inicio de la máquina, de manera que el sistema de ficheros debería haber sido correctamente montado de forma automática en /mnt/iscsi, por lo que vamos a comprobarlo listando una vez más los dispositivos de bloques existentes en la máquina cliente, haciendo para ello uso del comando: root@iscsic1:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 19.8G 0 disk ├─sda1 8:1 0 18.8G 0 part / ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 1021M 0 part [SWAP] sdb 8:16 0 1G 0 disk /mnt/iscsi Efectivamente, se ha hecho un login de forma automática en el target previamente generado en la máquina servidora, además de haberse montado el sistema de ficheros del dispositivo de bloques compartido en el punto de montaje especificado. De igual forma, vamos a listar el contenido de dicho directorio para verificar que el fichero previamente creado sigue existiendo en el mismo, ejecutando para ello el comando: root@iscsic1:~# ls -l /mnt/iscsi/ total 20 drwx------ 2 root root 16384 Feb 11 14:07 lost+found -rw-r--r-- 1 root root 6 Feb 11 14:08 prueba.txt Como se puede apreciar, el contenido sigue siendo exactamente el mismo, por lo que podemos concluir que toda la configuración llevada a cabo hasta ahora ha funcionado tal y como debería. La segunda prueba que voy a hacer consistirá en crear un nuevo target que contendrá dos unidades lógicas (LUN), que posteriormente se compartirá con la máquina cliente windows pero utilizando la autenticación CHAP que nos proporciona el protocolo iSCSI. Me cuento actualmente haciendo uso de la máquina servidora iscsis1, de manera que todo está listo para proceder con la creación del segundo target, que contendrá a los discos sdc y sdd como LUN. Lo primero que haremos, como ya sabemos, es generar un nuevo fichero de extensión .conf dentro de /etc/tgt/conf.d/, haciendo para ello uso del comando: root@iscsis1:~# nano /etc/tgt/conf.d/target2.conf En mi caso, el contenido a introducir dentro de dicho fichero es el siguiente: &lt;target iqn.2021-02.com.alvarovf:target2&gt; driver iscsi controller_tid 2 backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VB23af71a2-40586852 backing-store /dev/disk/by-id/ata-VBOX_HARDDISK_VBa2232259-3b0dda66 incominguser alvaro pruebaiscsi2021 &lt;/target&gt; Donde: incominguser: Definimos un usuario y una contraseña que tendrá permisos para hacer uso del target (conocido como autenticación CHAP). En este caso, existirá un usuario alvaro con contraseña pruebaiscsi2021 que podrá utilizar dicho target. Nuestro segundo target ya ha sido definido, sin embargo, al haberlo hecho en un fichero de configuración, todavía no ha sido cargado en memoria y por tanto, no se encuentra activo. Para ello, tendremos que reiniciar el servicio para que así detecte los nuevos cambios, haciendo para ello uso del comando: root@iscsis1:~# systemctl restart tgt Una vez que el reinicio haya finalizado, podremos listar todos los targets actualmente existentes ejecutando para ello la instrucción: root@iscsis1:~# tgtadm --lld iscsi --op show --mode target Target 1: iqn.2021-02.com.alvarovf:target1 System information: Driver: iscsi State: ready I_T nexus information: I_T nexus: 1 Initiator: iqn.1993-08.org.debian:01:6c92b3891490 alias: iscsic1 Connection: 0 IP Address: 192.168.1.137 LUN information: LUN: 0 Type: controller SCSI ID: IET 00010000 SCSI SN: beaf10 Size: 0 MB, Block size: 1 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: null Backing store path: None Backing store flags: LUN: 1 Type: disk SCSI ID: IET 00010001 SCSI SN: beaf11 Size: 1074 MB, Block size: 512 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: rdwr Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBc912936c-7d03844e Backing store flags: Account information: ACL information: ALL Target 2: iqn.2021-02.com.alvarovf:target2 System information: Driver: iscsi State: ready I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: IET 00020000 SCSI SN: beaf20 Size: 0 MB, Block size: 1 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: null Backing store path: None Backing store flags: LUN: 1 Type: disk SCSI ID: IET 00020001 SCSI SN: beaf21 Size: 1074 MB, Block size: 512 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: rdwr Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VB23af71a2-40586852 Backing store flags: LUN: 2 Type: disk SCSI ID: IET 00020002 SCSI SN: beaf22 Size: 1074 MB, Block size: 512 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: rdwr Backing store path: /dev/disk/by-id/ata-VBOX_HARDDISK_VBa2232259-3b0dda66 Backing store flags: Account information: alvaro ACL information: ALL Como se puede apreciar, sin entrar en demasiado detalle, se ha definido un segundo target iqn.2021-02.com.alvarovf:target2 que se encuentra actualmente activo (ready) que cuenta con un total de 3 unidades lógicas (LUN): LUN 0: Definida automáticamente y conocida como unidad lógica de control, que contiene las características del target. LUN 1: Definida por nosotros, de tipo disco (disk) y con un tamaño total de 1074 MB, siendo la ruta del mismo el identificador único del disco sdc. LUN 2: Definida por nosotros, de tipo disco (disk) y con un tamaño total de 1074 MB, siendo la ruta del mismo el identificador único del disco sdd. Nuestra labor en la máquina servidora iscsis1 ha finalizado, de manera que vamos a dar paso a la máquina cliente windows, desde la que realizaremos la conexión a dicho target. Lo primero que haremos, como es lógico, es acceder a la aplicación que nos permitirá actuar como cliente iSCSI, de nombre iSCSI Initiator. Para ello, haremos una búsqueda de la misma mediante el explorador: Cuando nos encontremos dentro de la misma, tendremos que hacer un discovery, que consistirá en conectarse a la máquina servidora en el puerto TCP que utiliza el protocolo por defecto (3260) y pedirle una lista de todos los targets disponibles, accediendo para ello al apartado Discovery en el menú superior: Dentro de dicho apartado, tendremos que pulsar en Discover Portal… para así añadir un nuevo servidor (portal) destino, que nos abrirá una ventana de la siguiente forma: Como se puede suponer, tendremos que indicar la dirección IP de la máquina servidora iscsis1, así como el puerto en el que está escuchando dichas peticiones, que es el utilizado por defecto. Si tras ello volvemos al apartado Targets, podremos apreciar lo siguiente: En este caso, como era de esperar, existen un total de dos targets disponibles, que son aquellos que manualmente hemos generado, con IQN asociados iqn.2021-02.com.alvarovf:target1 y iqn.2021-02.com.alvarovf:target2, respectivamente. Sin embargo, lo que acabamos de hacer era simplemente para obtener información, pues todavía no nos hemos conectado al segundo target (login) para poder hacer uso de forma remota de los dispositivos de bloques asociados. Para ello, lo seleccionaremos y pulsaremos en Connect: Dado que estamos haciendo uso de una autenticación CHAP, no bastará con pulsar en OK para realizar la conexión, sino que tendremos que introducir las credenciales del usuario con permisos para ello. Simplemente, tendremos que pulsar en Advanced y se nos abrirá una nueva ventana de la siguiente forma: Como se puede apreciar, hemos marcado la casilla Enable CHAP log on y hemos especificado las siguientes credenciales en el mismo, que tendrán que coincidir con las asignadas en el momento de la creación del target: Name: alvaro Target secret: pruebaiscsi2021 Tras ello, pulsaremos en OK para realizar la conexión y podremos apreciar lo siguiente: En un principio, la conexión al segundo target ha sido exitosa, de manera que los dos dispositivos de bloques deben estar ahora disponibles para su uso íntegro desde la máquina cliente windows. Para verificarlo, accederemos a Create and format hard disk partitions, haciendo para ello una búsqueda mediante el explorador, en caso de ser necesario: Cuando hayamos accedido, nos aparecerá una ventana emergente indicando que se han detectado dos nuevos discos que no se encuentran actualmente inicializados ni tampoco cuentan con tabla de particiones, de manera que la crearemos en su interior pulsando para ello en OK: Los discos ya habrán sido iniciados y tendrán una tabla de particiones en su interior, de manera que simplemente queda alojar un sistema de ficheros NTFS en los mismos. Para ello, haremos click derecho sobre el primero de ellos y pulsaremos en New Simple Volume…: Tras ello se abrirá una nueva ventana emergente que nos permitirá continuar con el proceso, consistente como suele ser común en Windows, en pulsar en Siguiente en reiteradas ocasiones. Repetiremos el mismo procedimiento para el segundo disco, de manera que el resultado final debe ser similar al siguiente: Efectivamente, los discos ya cuentan con un sistema de ficheros NTFS en su interior, por lo que estarán totalmente operativos y listos para su uso. Para verificarlo de nuevo, accederemos al explorador de Windows y podremos apreciar que las dos nuevas unidades aparecen ahí y podríamos empezar a hacer uso de las mismas desde este momento: A mi parecer, el artículo ha sido bastante claro y conciso, mencionando en todo momento las numerosas ventajas que nos ofrece este simple pero útil protocolo para compartir dispositivos de bloques a través de la red, de manera que ya queda ser creativo y aplicarlo en aquellos escenarios en los que pueda ofrecernos ventajas. ¡Un saludo!]]></summary></entry><entry><title type="html">Recolección de métricas con Telegraf, InfluxDB y Grafana</title><link href="https://www.alvarovf.com/sistemas/openstack/2021/02/09/metricas-telegraf-influxdb-grafana.html" rel="alternate" type="text/html" title="Recolección de métricas con Telegraf, InfluxDB y Grafana" /><published>2021-02-09T09:35:00+00:00</published><updated>2021-02-09T09:35:00+00:00</updated><id>https://www.alvarovf.com/sistemas/openstack/2021/02/09/metricas-telegraf-influxdb-grafana</id><content type="html" xml:base="https://www.alvarovf.com/sistemas/openstack/2021/02/09/metricas-telegraf-influxdb-grafana.html"><![CDATA[<p>El objetivo de esta tarea es el de continuar con la configuración del escenario de trabajo previamente generado en <strong>OpenStack</strong>, concretamente, llevando a cabo una instalación de un sistema de recolección de métricas sobre el mismo, incluyendo a su vez, las métricas de la máquina VPS contratada en OVH que en otros artículos hemos tratado.</p>

<p>Dicho sistema nos permitirá recolectar, centralizar y filtrar determinados aspectos sobre el rendimiento de las máquinas para su posterior representación gráfica que permita controlar la evolución temporal de parámetros esenciales de todos los servidores, permitiendo por tanto detectar anomalías en los mismos.</p>

<p>El sistema de recolección de métricas que vamos a instalar es una pila compuesta por los siguientes elementos:</p>

<ul>
  <li>
    <p><strong>InfluxDB</strong>: Base de datos basada en series de tiempo (<em>time-series database</em>), pensada para almacenar y tratar de la manera más eficiente posible una gran cantidad de información por segundo, permitiendo a su vez realizar análisis de dichos datos en tiempo real (medias, mínimos, máximos, búsquedas en el tiempo…).</p>
  </li>
  <li>
    <p><strong>Telegraf</strong>: Servicio que recopila y envía datos de métricas de diferentes sistemas, como por ejemplo uso de disco, carga del sistema, uso de la memoria RAM, carga de la CPU… La salida de Telegraf, por norma general, se envía a una base de datos de tipo series de tiempo como <strong>InfluxDB</strong>. Cuenta con una enorme lista de <em>plugins</em> para aumentar sus funcionalidades.</p>
  </li>
  <li>
    <p><strong>Grafana</strong>: Servicio que permite crear cuadros de mando y gráficos a partir de múltiples fuentes, incluidas las bases de datos de series de tiempo como <strong>InfluxDB</strong>. Permite la creación de usuarios con diferentes privilegios dentro de la aplicación, por lo que es posible compartir con otros miembros los paneles generados.</p>
  </li>
</ul>

<p><img src="https://i.ibb.co/VVvBSqS/tig-monitor-logic.png" alt="diagrama1" title="Diagrama sistema" /></p>

<p>Sin embargo, en este caso contamos con dos escenarios existentes, uno privado y uno público:</p>

<ul>
  <li><strong>OpenStack</strong>: Escenario privado en el que se encuentran ubicadas las máquinas <strong>Dulcinea</strong> (Debian 10), <strong>Sancho</strong> (Ubuntu 20.04), <strong>Quijote</strong> (CentOS 8) y <strong>Freston</strong> (Debian 10), que como se puede suponer, cuentan con direccionamiento privado.</li>
  <li><strong>OVH</strong>: Escenario público en el que se encuentra ubicada la <strong>VPS</strong> (Debian 10), que como se puede suponer, cuenta con direccionamiento público.</li>
</ul>

<p>Para entender el problema existente, es necesario recalcar una vez más cómo funciona <strong>Telegraf</strong>. Dicho servicio recolecta las métricas especificadas y posteriormente abre una conexión con el servidor configurado que será aquel que esté ejecutando la base de datos en la que se va a almacenar la información. En otras palabras, el cliente Telegraf necesitará alcanzar a la máquina en la que se almacenarán las métricas.</p>

<p>Si nuestra intención fuese recolectar métricas únicamente en el escenario privado, no habría problema, sin embargo, la VPS no será capaz de alcanzar las máquinas ubicadas en el escenario OpenStack privado, al contar las mismas con un direccionamiento no alcanzable desde el exterior.</p>

<p>Para solventar esta situación, tenemos dos opciones:</p>

<ul>
  <li>Instalar InfluxDB en una de las máquinas OpenStack y posteriormente configurar una VPN en la VPS para que se pueda alcanzar dicha máquina desde el exterior.</li>
  <li>Instalar InfluxDB en la VPS, que será alcanzable desde cualquiera de las máquinas OpenStack sin necesidad de llevar a cabo ninguna configuración adicional.</li>
</ul>

<p>En mi caso, voy a optar por la segunda opción dada la sencillez que me aporta, evitando por tanto el hecho de tener que llevar a cabo configuraciones adicionales. Es por ello que la instalación de <strong>InfluxDB</strong>, <strong>Telegraf</strong> y <strong>Grafana</strong> se llevará a cabo en la VPS, mientras que en el resto de máquinas únicamente será necesario instalar <strong>Telegraf</strong> para así enviar dichas métricas a la base de datos alojada en la VPS.</p>

<h2 id="influxdb">InfluxDB</h2>

<p>El primer paso consistirá en instalar la paquetería necesaria en la VPS para poder trabajar con <strong>InfluxDB</strong>, los cimientos de este sistema, que como ya hemos mencionado, será el gestor de bases de datos que almacenará las métricas recolectadas.</p>

<p>Dado que dicho paquete cuenta con una gran diferencia de versión en los repositorios de Debian con respecto a <em>upstream</em>, procederemos a descargarlo de los repositorios oficiales de <em>InfluxData</em>.</p>

<p>Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">echo</span> <span class="s2">"deb https://repos.influxdata.com/debian buster stable"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/influxdb.list</code></pre></figure>

<p>De otro lado, tendremos que añadir también la clave pública GPG de <em>InfluxData</em> a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de InfluxDB que vamos a descargar, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# wget <span class="nt">-qO-</span> https://repos.influxdata.com/influxdb.key | apt-key add -
OK</code></pre></figure>

<p>Tras ello, podremos dar paso a la instalación de InfluxDB, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>influxdb</code></pre></figure>

<p>El paquete <strong>influxdb</strong> ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl status influxdb
● influxdb.service - InfluxDB is an open-source, distributed, <span class="nb">time </span>series database
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/influxdb.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: inactive <span class="o">(</span>dead<span class="o">)</span>
     Docs: https://docs.influxdata.com/influxdb/</code></pre></figure>

<p>Como se puede apreciar, el servicio se encuentra habilitado para arrancar junto a la máquina (<em>enabled</em>), sin embargo, actualmente no se encuentra activo (<em>inactive</em>), de manera que procederemos a levantarlo manualmente, ejecutando para ello la siguiente instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl start influxdb</code></pre></figure>

<p>Si verificamos una vez más el estado del servicio, haciendo para ello uso del comando utilizado con anterioridad, se nos mostrará lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl status influxdb
● influxdb.service - InfluxDB is an open-source, distributed, <span class="nb">time </span>series database
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/influxdb.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Fri 2021-02-05 18:28:43 CET<span class="p">;</span> 2s ago
     Docs: https://docs.influxdata.com/influxdb/
 Main PID: 5255 <span class="o">(</span>influxd<span class="o">)</span>
    Tasks: 7 <span class="o">(</span>limit: 2318<span class="o">)</span>
   Memory: 13.8M
   CGroup: /system.slice/influxdb.service
           └─5255 /usr/bin/influxd <span class="nt">-config</span> /etc/influxdb/influxdb.conf</code></pre></figure>

<p>Efectivamente, el servicio se encuentra ahora activo y listo para su uso.</p>

<p>Como consecuencia del proceso que está actualmente ejecutándose, se habrán abierto dos <em>sockets TCP/IP</em> en los puertos por defecto de InfluxDB (<strong>8086</strong> y <strong>8088</strong>) que estarán escuchando peticiones, así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tlnp</span> | egrep <span class="s1">'influx'</span>
tcp        0      0 127.0.0.1:8088          0.0.0.0:<span class="k">*</span>               LISTEN      5255/influxd
tcp6       0      0 :::8086                 :::<span class="k">*</span>                    LISTEN      5255/influxd</code></pre></figure>

<ul>
  <li><strong>-t</strong>: Filtramos únicamente para las conexiones que utilizan el protocolo TCP.</li>
  <li><strong>-l</strong>: Filtramos únicamente para los <em>sockets</em> que están actualmente escuchando peticiones (<strong>State = LISTEN</strong>).</li>
  <li><strong>-n</strong>: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos.</li>
  <li><strong>-p</strong>: Indicamos que muestre el PID y el nombre del proceso al que pertenece dicho <em>socket</em>.</li>
</ul>

<p>Efectivamente, el proceso <strong>influxd</strong> está escuchando peticiones en todas las interfaces de la máquina en el puerto <strong>8086</strong>, que es aquel que se expone al exterior para recibir peticiones HTTP y del que por tanto, haremos uso para recibir las métricas del resto de máquinas.</p>

<p>De otro lado, el puerto <strong>8088</strong> se encuenta limitado a la interfaz <em>loopback</em> de la máquina, ya que es el que se utiliza para interactuar de forma directa con la base de datos en operaciones como copias de seguridad y restauraciones, que no debe ser accesible desde el exterior.</p>

<p>Tras ello, procederemos a abrir una <em>shell</em> de <strong>influx</strong> para así poder gestionar el motor, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# influx
Connected to http://localhost:8086 version 1.8.4
InfluxDB shell version: 1.8.4</code></pre></figure>

<p>Como se puede apreciar, la <em>influx shell</em> se ha abierto correctamente y está lista para su uso.</p>

<p>Lo primero que haremos estando dentro del servidor de bases de datos será crear una nueva base de datos en la que almacenar las métricas, valga la redundancia. En este caso, de nombre <strong>telegraf</strong>. Para ello, haremos uso de la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> CREATE DATABASE telegraf</code></pre></figure>

<p>La base de datos en cuestión habrá sido generada, pero para verificarlo, vamos a listar todas las bases de datos existentes, ejecutando para ello la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> SHOW DATABASES
name: databases
name
<span class="nt">----</span>
_internal
telegraf</code></pre></figure>

<p>Efectivamente, la base de datos <strong>telegraf</strong> ha sido correctamente generada, sin embargo, de nada nos sirve tener una base de datos si no tenemos un usuario que pueda escribir en la misma.</p>

<p>Únicamente necesitamos un usuario, ya que la distinción entre las máquinas que envían sus métricas se hace mediante la columna <em>host</em> del registro almacenado, que como se puede suponer, contendrá el nombre de la máquina que ha enviado el registro.</p>

<p>En este caso, vamos a crear un usuario de nombre “<strong>telegraf</strong>” cuya contraseña, como es lógico, censuraré por seguridad. Para ello, haremos uso de la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> CREATE USER telegraf WITH PASSWORD <span class="s1">'XXXXXXXXXX'</span></code></pre></figure>

<p>El usuario en cuestión habrá sido generado, pero para verificarlo, vamos a listar todos los usuarios existentes, ejecutando para ello la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> SHOW USERS
user     admin
<span class="nt">----</span>     <span class="nt">-----</span>
telegraf <span class="nb">false</span></code></pre></figure>

<p>Efectivamente, el usuario <strong>telegraf</strong> ha sido correctamente generado.</p>

<p>Una vez realizadas las oportunas modificaciones, podremos salir de <em>influx</em> haciendo uso de la instrucción <code class="language-plaintext highlighter-rouge">exit</code>, con la finalidad de continuar ahora con la instalación de <strong>Telegraf</strong>.</p>

<h2 id="telegraf">Telegraf</h2>

<h3 id="vps">VPS</h3>

<p>El segundo paso consistirá en instalar la paquetería necesaria en todas las máquinas para poder recolectar nuestras métricas gracias a <strong>Telegraf</strong> y enviarlas a la base de datos previamente generada.</p>

<p>Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de <em>InfluxData</em>.</p>

<p>Como podemos apreciar, dicho <em>software</em> es de la misma organización que <strong>InfluxDB</strong>, de manera que nos servirá la anterior clave pública GPG y repositorio instalados para realizar la descarga e instalación del paquete, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt <span class="nb">install </span>telegraf</code></pre></figure>

<p>El paquete <strong>telegraf</strong> ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Fri 2021-02-05 18:35:08 CET<span class="p">;</span> 3s ago
     Docs: https://github.com/influxdata/telegraf
 Main PID: 5480 <span class="o">(</span>telegraf<span class="o">)</span>
    Tasks: 6 <span class="o">(</span>limit: 2318<span class="o">)</span>
   Memory: 19.6M
   CGroup: /system.slice/telegraf.service
           └─5480 /usr/bin/telegraf <span class="nt">-config</span> /etc/telegraf/telegraf.conf <span class="nt">-config-directory</span> /etc/telegraf/telegraf.d</code></pre></figure>

<p>Efectivamente, el servicio se encuentra activo y listo para su uso, sin embargo, no se encuentra configurado para actuar de la manera que nosotros necesitamos.</p>

<p>Para solucionarlo, procederemos a modificar el fichero <strong>/etc/telegraf/telegraf.conf</strong>, en el que estableceremos los parámetros deseados, como por ejemplo la base de datos remota, el nombre de la máquina… ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/telegraf/telegraf.conf</code></pre></figure>

<p>Dentro del mismo, podremos encontrar las siguientes secciones:</p>

<ul>
  <li><strong>Output Plugins</strong>: Envía las métricas recolectadas a diferentes destinos, en mi caso a <em>InfluxDB</em>.</li>
  <li><strong>Processor Plugins</strong>: Transforma, decora y filtra las métricas recolectadas.</li>
  <li><strong>Aggregator Plugins</strong>: Crea conjuntos de métricas, por ejemplo, permitiendo hacer una media, mínimo, máximo…</li>
  <li><strong>Input Plugins</strong>: Recolecta las métricas especificadas del sistema o servicios.</li>
</ul>

<p>En este caso, tendremos que buscar la sección de nombre <strong>[[outputs.influxdb]]</strong> dentro de <strong>Output Plugins</strong> para indicar dentro de la misma los parámetros necesarios para llevar a cabo la conexión con InfluxDB.</p>

<ul>
  <li>El primer parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto:</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># urls = ["http://127.0.0.1:8086"]</span></code></pre></figure>

<p>Como se puede suponer, tendremos que descomentarlo e indicar la dirección y el puerto en la que se aloja la base de datos InfluxDB, que en este caso es correcta, pues se está ejecutando en <strong><em>localhost</em></strong> en el puerto <strong>8086</strong>.</p>

<p>En el resto de máquinas, tendremos que introducir la dirección IP pública de dicha VPS, es decir, <strong>51.210.109.246</strong>. Por tanto, el resultado final de dicha directiva sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">urls <span class="o">=</span> <span class="o">[</span><span class="s2">"http://127.0.0.1:8086"</span><span class="o">]</span></code></pre></figure>

<ul>
  <li>El segundo parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto:</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># database = "telegraf"</span></code></pre></figure>

<p>Como se puede suponer, tendremos que descomentarlo e indicar el nombre de la base de datos InfluxDB que previamente hemos generado, que una vez más, es correcto. Por tanto, el resultado final de dicha directiva sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">database <span class="o">=</span> <span class="s2">"telegraf"</span></code></pre></figure>

<ul>
  <li>El tercer parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto:</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># skip_database_creation = false</span></code></pre></figure>

<p>Como se puede suponer, tendremos que descomentarlo y asignarle el valor <strong>true</strong> para que así no trate de generar la base de datos InfluxDB, ya que previamente la hemos generado nosotros manualmente. Por tanto, el resultado final de dicha directiva sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">skip_database_creation <span class="o">=</span> <span class="nb">true</span></code></pre></figure>

<ul>
  <li>El cuarto parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto:</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># timeout = "5s"</span></code></pre></figure>

<p>Como se puede suponer, tendremos que descomentarlo e indicar el valor de tiempo que queremos que transcurra antes de que la petición HTTP a la base de datos destino expire en caso de no haber sido alcanzada, que en este caso, 5 segundos es un valor que me parece correcto. Por tanto, el resultado final de dicha directiva sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nb">timeout</span> <span class="o">=</span> <span class="s2">"5s"</span></code></pre></figure>

<ul>
  <li>Por último, el quinto y sexto parámetro que modificaremos dentro de dicha sección serán los que por defecto tienen el siguiente aspecto:</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># username = "telegraf"
</span>
<span class="c"># password = "metricsmetricsmetricsmetrics"</span></code></pre></figure>

<p>Como se puede suponer, tendremos que descomentarlos e indicar el nombre del usuario que previamente hemos generado en <strong>InfluxDB</strong>, así como la contraseña del mismo, que censuraré por seguridad. Por tanto, el resultado final de dichas directivas sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">username <span class="o">=</span> <span class="s2">"telegraf"</span>
password <span class="o">=</span> <span class="s2">"XXXXXXXXXX"</span></code></pre></figure>

<p>Listo, la configuración básica del cliente <strong>Telegraf</strong> ha finalizado.</p>

<p>Es importante mencionar que <strong>Telegraf</strong> es un <em>software</em> realmente grande y nos ofrece una cantidad descomunal de posibilidades de recolección de métricas (<em>plugins</em>). Tratar todas ellas en un artículo sería totalmente inviable, de manera que aquellas que hemos modificado son las justas y necesarias para el correcto funcionamiento básico del mismo, ya que se incluyen las más esenciales habilitadas por defecto (uso de disco, carga del sistema, uso de la memoria RAM, carga de la CPU…).</p>

<p>Otros dos parámetros muy importantes se encuentran en la sección <strong>[agent]</strong>, siendo los siguientes:</p>

<ul>
  <li><strong>interval</strong>: Frecuencia con la que queremos que se recolecten las métricas, que por defecto son 10 segundos. Mientras menor sea dicho valor, más exacto será nuestro sistema de recolección de métricas, pero a su vez, mayor será el flujo de datos que viajará entre las máquinas.</li>
  <li><strong>hostname</strong>: En caso de no especificar un <em>hostname</em> en concreto, se utilizará aquel que la máquina tiene asignado, que como se puede suponer, tiene la finalidad de identificar de forma única a cada máquina dentro del sistema de recolección de métricas.</li>
</ul>

<p>Tras ello, guardaremos los cambios y dado que hemos modificado el fichero de configuración de un servicio, tendremos que reiniciarlo para así cargar la nueva configuración en memoria, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@sancho:~# systemctl restart telegraf</code></pre></figure>

<p>Dado que el proceso de instalación de <strong>Telegraf</strong> es prácticamente igual en todas las máquinas, vamos a hacer un pequeño resumen para no repetir la misma explicación para cada una de ellas:</p>

<ul>
  <li>Añadimos la clave pública GPG y el correspondiente repositorio de <em>InfluxData</em>.</li>
  <li>Descargamos e instalamos el paquete <em>telegraf</em>, actualizando previamente la lista de la paquetería disponible.</li>
  <li>Comprobamos que el servicio se encuentra activo y habilitado durante el arranque.</li>
  <li>Llevamos a cabo las modificaciones previamente mencionadas en el fichero <strong>/etc/telegraf/telegraf.conf</strong>.</li>
  <li>Reiniciamos el servicio para cargar la nueva configuración en memoria.</li>
</ul>

<h3 id="dulcinea">Dulcinea</h3>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@dulcinea:~# wget <span class="nt">-qO-</span> https://repos.influxdata.com/influxdb.key | apt-key add -
OK

root@dulcinea:~# <span class="nb">echo</span> <span class="s2">"deb https://repos.influxdata.com/debian buster stable"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/influxdb.list

root@dulcinea:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>telegraf

root@dulcinea:~# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Sat 2021-02-06 11:06:39 CET<span class="p">;</span> 4s ago
     Docs: https://github.com/influxdata/telegraf
 Main PID: 16138 <span class="o">(</span>telegraf<span class="o">)</span>
    Tasks: 7 <span class="o">(</span>limit: 562<span class="o">)</span>
   Memory: 13.7M
   CGroup: /system.slice/telegraf.service
           └─16138 /usr/bin/telegraf <span class="nt">-config</span> /etc/telegraf/telegraf.conf <span class="nt">-config-directory</span> /etc/telegraf/telegraf.d

root@dulcinea:~# nano /etc/telegraf/telegraf.conf

<span class="c"># urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"]
</span>
<span class="c"># database = "telegraf" -&gt; database = "telegraf"
</span>
<span class="c"># skip_database_creation = false -&gt; skip_database_creation = true
</span>
<span class="c"># timeout = "5s" -&gt; timeout = "5s"
</span>
<span class="c"># username = "telegraf" -&gt; username = "telegraf"
</span>
<span class="c"># password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX"
</span>

root@dulcinea:~# systemctl restart telegraf</code></pre></figure>

<h3 id="sancho">Sancho</h3>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@sancho:~# wget <span class="nt">-qO-</span> https://repos.influxdata.com/influxdb.key | apt-key add -
OK

root@sancho:~# <span class="nb">echo</span> <span class="s2">"deb https://repos.influxdata.com/ubuntu focal stable"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/influxdb.list

root@sancho:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>telegraf

root@sancho:~# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
     Loaded: loaded <span class="o">(</span>/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
     Active: active <span class="o">(</span>running<span class="o">)</span> since Sat 2021-02-06 11:14:38 CET<span class="p">;</span> 24s ago
       Docs: https://github.com/influxdata/telegraf
   Main PID: 18572 <span class="o">(</span>telegraf<span class="o">)</span>
      Tasks: 7 <span class="o">(</span>limit: 533<span class="o">)</span>
     Memory: 42.9M
     CGroup: /system.slice/telegraf.service
             └─18572 /usr/bin/telegraf <span class="nt">-config</span> /etc/telegraf/telegraf.conf <span class="nt">-config-directory</span> /etc/telegraf/telegraf.d

root@sancho:~# nano /etc/telegraf/telegraf.conf

<span class="c"># urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"]
</span>
<span class="c"># database = "telegraf" -&gt; database = "telegraf"
</span>
<span class="c"># skip_database_creation = false -&gt; skip_database_creation = true
</span>
<span class="c"># timeout = "5s" -&gt; timeout = "5s"
</span>
<span class="c"># username = "telegraf" -&gt; username = "telegraf"
</span>
<span class="c"># password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX"
</span>

root@sancho:~# systemctl restart telegraf</code></pre></figure>

<h3 id="quijote">Quijote</h3>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@quijote ~]# vi /etc/yum.repos.d/influxdb.repo

<span class="o">[</span>influxdb]
name <span class="o">=</span> InfluxDB Repository - RHEL 
baseurl <span class="o">=</span> https://repos.influxdata.com/rhel/7/x86_64/stable/
enabled <span class="o">=</span> 1
gpgcheck <span class="o">=</span> 1
gpgkey <span class="o">=</span> https://repos.influxdata.com/influxdb.key

<span class="o">[</span>root@quijote ~]# dnf <span class="nb">install </span>telegraf

<span class="o">[</span>root@quijote ~]# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
   Loaded: loaded <span class="o">(</span>/usr/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: disabled<span class="o">)</span>
   Active: inactive <span class="o">(</span>dead<span class="o">)</span>
     Docs: https://github.com/influxdata/telegraf

<span class="o">[</span>root@quijote ~]# systemctl start telegraf

<span class="o">[</span>root@quijote ~]# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
   Loaded: loaded <span class="o">(</span>/usr/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: disabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Sat 2021-02-06 11:24:15 CET<span class="p">;</span> 1s ago
     Docs: https://github.com/influxdata/telegraf
 Main PID: 5932 <span class="o">(</span>telegraf<span class="o">)</span>
    Tasks: 4 <span class="o">(</span>limit: 2635<span class="o">)</span>
   Memory: 52.4M
   CGroup: /system.slice/telegraf.service
           └─5932 /usr/bin/telegraf <span class="nt">-config</span> /etc/telegraf/telegraf.conf <span class="nt">-config-directory</span> /etc/telegraf/telegraf.d

<span class="o">[</span>root@quijote ~]# vi /etc/telegraf/telegraf.conf

<span class="c"># urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"]
</span>
<span class="c"># database = "telegraf" -&gt; database = "telegraf"
</span>
<span class="c"># skip_database_creation = false -&gt; skip_database_creation = true
</span>
<span class="c"># timeout = "5s" -&gt; timeout = "5s"
</span>
<span class="c"># username = "telegraf" -&gt; username = "telegraf"
</span>
<span class="c"># password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX"
</span>

<span class="o">[</span>root@quijote ~]# systemctl restart telegraf</code></pre></figure>

<h3 id="freston">Freston</h3>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@freston:~# wget <span class="nt">-qO-</span> https://repos.influxdata.com/influxdb.key | apt-key add -
OK

root@freston:~# <span class="nb">echo</span> <span class="s2">"deb https://repos.influxdata.com/debian buster stable"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/influxdb.list

root@freston:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>telegraf

root@freston:~# systemctl status telegraf
● telegraf.service - The plugin-driven server agent <span class="k">for </span>reporting metrics into InfluxDB
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/telegraf.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Sat 2021-02-06 11:27:47 CET<span class="p">;</span> 5s ago
     Docs: https://github.com/influxdata/telegraf
 Main PID: 15462 <span class="o">(</span>telegraf<span class="o">)</span>
    Tasks: 7 <span class="o">(</span>limit: 562<span class="o">)</span>
   Memory: 17.0M
   CGroup: /system.slice/telegraf.service
           └─15462 /usr/bin/telegraf <span class="nt">-config</span> /etc/telegraf/telegraf.conf <span class="nt">-config-directory</span> /etc/telegraf/telegraf.d

root@freston:~# nano /etc/telegraf/telegraf.conf

<span class="c"># urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"]
</span>
<span class="c"># database = "telegraf" -&gt; database = "telegraf"
</span>
<span class="c"># skip_database_creation = false -&gt; skip_database_creation = true
</span>
<span class="c"># timeout = "5s" -&gt; timeout = "5s"
</span>
<span class="c"># username = "telegraf" -&gt; username = "telegraf"
</span>
<span class="c"># password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX"
</span>

root@freston:~# systemctl restart telegraf</code></pre></figure>

<h2 id="grafana">Grafana</h2>

<p>El tercer y último paso consistirá en instalar la paquetería necesaria en la VPS para poder visualizar de forma gráfica las métricas recolectadas en la base de datos gracias a <strong>Grafana</strong> y tratar las mismas como nos sea necesario.</p>

<p>Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de <em>Grafana</em>.</p>

<p>Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">echo</span> <span class="s2">"deb https://packages.grafana.com/enterprise/deb stable main"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/grafana.list</code></pre></figure>

<p>De otro lado, tendremos que añadir también la clave pública GPG de <em>Grafana</em> a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de Grafana que vamos a descargar, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# wget <span class="nt">-q</span> <span class="nt">-O</span> - https://packages.grafana.com/gpg.key | apt-key add -
OK</code></pre></figure>

<p>Tras ello, podremos dar paso a la instalación de Grafana, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>grafana</code></pre></figure>

<p>El paquete <strong>grafana</strong> ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl status grafana-server
● grafana-server.service - Grafana instance
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/grafana-server.service<span class="p">;</span> disabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: inactive <span class="o">(</span>dead<span class="o">)</span>
     Docs: http://docs.grafana.org</code></pre></figure>

<p>Como se puede apreciar, el servicio no se encuentra habilitado para arrancar junto a la máquina (<em>disabled</em>), además, tampoco se encuentra activo (<em>inactive</em>), de manera que procederemos a habilitarlo y levantarlo manualmente, ejecutando para ello las siguientes instrucciones:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl start grafana-server
root@vps:~# systemctl <span class="nb">enable </span>grafana-server</code></pre></figure>

<p>Si verificamos una vez más el estado del servicio, haciendo para ello uso del comando utilizado con anterioridad, se nos mostrará lo siguiente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl status grafana-server
● grafana-server.service - Grafana instance
   Loaded: loaded <span class="o">(</span>/lib/systemd/system/grafana-server.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Fri 2021-02-05 18:08:19 CET<span class="p">;</span> 22s ago
     Docs: http://docs.grafana.org
 Main PID: 3994 <span class="o">(</span>grafana-server<span class="o">)</span>
    Tasks: 7 <span class="o">(</span>limit: 2318<span class="o">)</span>
   Memory: 27.2M
   CGroup: /system.slice/grafana-server.service
           └─3994 /usr/sbin/grafana-server <span class="nt">--config</span><span class="o">=</span>/etc/grafana/grafana.ini <span class="nt">--pidfile</span><span class="o">=</span>/var/run/grafana/grafana-server.pid <span class="nt">--packaging</span><span class="o">=</span>deb cfg:default.paths.logs<span class="o">=</span>/var/log/grafana cfg:defaul</code></pre></figure>

<p>Efectivamente, el servicio se encuentra ahora habilitado, activo y listo para su uso.</p>

<p>Como consecuencia del proceso que está actualmente ejecutándose, se habrá abierto un <em>socket TCP/IP</em> en el puerto por defecto de Grafana (<strong>3000</strong>) que estará escuchando peticiones provenientes de todas las interfaces de la máquina, así que para verificarlo haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# netstat <span class="nt">-tlnp</span> | egrep <span class="s1">'grafana'</span>
tcp6       0      0 :::3000                 :::<span class="k">*</span>                    LISTEN      3994/grafana-server</code></pre></figure>

<p>Efectivamente, el proceso <strong>grafana-server</strong> está escuchando peticiones en todas las interfaces de la máquina en el puerto <strong>3000</strong>, que es aquel que se expone al exterior para poder acceder al panel de administración web acondicionado para ello.</p>

<p>Antes de continuar, es importante mencionar que la configuración por defecto de Grafana es poco restrictiva, pues permite a los usuarios registrarse de forma manual, que en mi caso no es lo que busco. Para evitarlo, tendremos que modificar el fichero de nombre <strong>/etc/grafana/grafana.ini</strong>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/grafana/grafana.ini</code></pre></figure>

<p>Dentro del mismo, tendremos que modificar los siguientes parámetros para deshabilitar el registro de usuarios y la creación de organizaciones:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="p">;</span>allow_sign_up <span class="o">=</span> <span class="nb">true</span> -&gt; allow_sign_up <span class="o">=</span> <span class="nb">false</span>
<span class="p">;</span>allow_org_create <span class="o">=</span> <span class="nb">true</span> -&gt; allow_org_create <span class="o">=</span> <span class="nb">false</span></code></pre></figure>

<p>Tras ello, guardaremos los cambios y dado que hemos modificado el fichero de configuración de un servicio, tendremos que reiniciarlo para así cargar la nueva configuración en memoria, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl restart grafana-server</code></pre></figure>

<p>Como anteriormente hemos mencionado, el servicio <strong>grafana-server</strong> se encuentra en ejecución en el puerto 3000, lo que supone que cada vez que queramos acceder al panel de administración web tendremos que conectarnos a dicho puerto de forma explícita (<a href="http://51.210.109.246:3000">http://51.210.109.246:3000</a>), que es algo que puede resultar un tanto incómodo.</p>

<p>Para solventar este “problema”, he decidido hacer uso del servidor web <strong>nginx</strong> alojado en dicha máquina que se encuentra actualmente escuchando peticiones en los puertos <strong>80</strong> (HTTP) y <strong>443</strong> (HTTPS), de manera que crearemos un VirtualHost accesible desde un determinado nombre de dominio y reenviaremos la petición entrante a través de un <em>proxy</em> inverso al puerto <strong>3000</strong>, consiguiendo por tanto utilizar los puertos HTTP y HTTPS para hacer uso del panel de administración web de Grafana.</p>

<p>El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS <strong>iesgn19.es</strong>, concretamente nombrando un nuevo servicio <strong>metricas</strong> mediante un registro <em>CNAME</em> al registro <em>A</em> que apunta a la dirección <strong>51.210.109.246</strong>, que será a través del cuál accederemos al VirtualHost en cuestión, tal y como podemos apreciar a continuación:</p>

<p><img src="https://i.ibb.co/zS0ZfXV/Captura-18.jpg" alt="dns1" title="Zona DNS" /></p>

<p>Como bien sabemos, si queremos hacer uso del protocolo HTTPS para dicho nombre de dominio, necesitaremos generar un certificado firmado por una autoridad certificadora de considerable reputación, así que en mi caso, he recurrido una vez más a <strong>Let’s Encrypt</strong>, siguiendo para ello los pasos indicados en el artículo <a href="https://www.alvarovf.com/seguridad/vps/2020/11/30/configuracion-https-nginx.html">Configuración de HTTPS en Nginx</a>, pues mencionar aquí el proceso se saldría completamente del objetivo del <em>post</em>.</p>

<p>Cuando nuestro certificado haya sido correctamente generado, todo estará listo para configurar el nuevo VirtualHost dentro del directorio <strong>/etc/nginx/sites-available/</strong>, cuyo nombre he decidido que en este caso sea <strong>metricas</strong>, de manera que ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# nano /etc/nginx/sites-available/metricas</code></pre></figure>

<p>Dentro del mismo, tendremos que crear dos directivas <strong>server</strong>, una para el VirtualHost accesible en el puerto 80 HTTP, dentro del cuál únicamente asignaremos el ServerName correspondiente y una redirección permanente para así forzar HTTPS. En la otra, para el VirtualHost accesible en el puerto 443 HTTPS, tendremos que configurar las siguientes directivas:</p>

<ul>
  <li><strong>server_name</strong>: Indicaremos el nombre de dominio a través del cuál accederemos al servidor.</li>
  <li><strong>ssl</strong>: Activa el motor SSL, necesario para hacer uso de HTTPS, por lo que su valor debe ser <strong>on</strong>.</li>
  <li><strong>ssl_certificate</strong>: Indicamos la ruta del certificado del servidor firmado por la <em>CA</em>. En este caso, <strong>/etc/letsencrypt/live/metricas.iesgn19.es/fullchain.pem</strong>.</li>
  <li><strong>ssl_certificate_key</strong>: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, <strong>/etc/letsencrypt/live/metricas.iesgn19.es/privkey.pem</strong>.</li>
  <li><strong>location</strong>: Especificamos las instrucciones para resolver una petición a la ruta introducida (en este caso, cualquier ruta, ya que hemos indicado la raíz y se aplica de forma recursiva a partir de la misma). Dentro de dicho bloque, definimos mediante una directiva <strong>proxy_pass</strong> a qué dirección se debe reenviar la petición, que en este caso, será al puerto <strong>3000</strong> de la máquina local.</li>
</ul>

<p>El resultado final sería:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">server <span class="o">{</span>
        listen 80<span class="p">;</span>
        listen <span class="o">[</span>::]:80<span class="p">;</span>

        server_name metricas.iesgn19.es<span class="p">;</span>

        <span class="k">return </span>301 https://<span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="o">}</span>

server <span class="o">{</span>
        listen 443 ssl http2<span class="p">;</span>
        listen <span class="o">[</span>::]:443 ssl http2<span class="p">;</span>

        server_name metricas.iesgn19.es<span class="p">;</span>

        ssl    on<span class="p">;</span>
        ssl_certificate    /etc/letsencrypt/live/metricas.iesgn19.es/fullchain.pem<span class="p">;</span>
        ssl_certificate_key    /etc/letsencrypt/live/metricas.iesgn19.es/privkey.pem<span class="p">;</span>

        location / <span class="o">{</span>
                proxy_pass http://localhost:3000<span class="p">;</span>
        <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Listo, toda la configuración necesaria ya ha sido realizada, así que únicamente queda habilitar dicho sitio y probar que realmente funciona. Para activar el sitio, a diferencia de <em>apache2</em> que contaba con una utilidad para ello, tendremos crear el enlace simbólico al fichero de configuración ubicado en <strong>/etc/nginx/sites-available/</strong> dentro de <strong>/etc/nginx/sites-enabled/</strong> de forma manual. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/metricas /etc/nginx/sites-enabled/</code></pre></figure>

<p>Al parecer, el sitio ha sido correctamente habilitado, pero para activar la nueva configuración, tendremos que volver a cargar la configuración del servicio <em>nginx</em>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# systemctl reload nginx</code></pre></figure>

<p>Una vez que la configuración del servicio se ha vuelto a cargar, vamos a listar el contenido de <strong>/etc/nginx/sites-enabled/</strong> para verificar que el correspondiente enlace simbólico ha sido correctamente creado. Para ello, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@vps:~# <span class="nb">ls</span> <span class="nt">-l</span> /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Nov  3 16:11 default -&gt; /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 33 Nov 11 11:21 drupal -&gt; /etc/nginx/sites-available/drupal
lrwxrwxrwx 1 root root 34 Nov  3 17:14 iesgn19 -&gt; /etc/nginx/sites-available/iesgn19
lrwxrwxrwx 1 root root 35 Feb  5 19:37 metricas -&gt; /etc/nginx/sites-available/metricas</code></pre></figure>

<p>Efectivamente, el nuevo sitio se encuentra actualmente activo, así que es hora de realizar la correspondiente prueba de acceso. Para ello, abriremos un navegador web y trataremos de acceder a <a href="https://metricas.iesgn19.es">https://metricas.iesgn19.es</a>:</p>

<p><img src="https://i.ibb.co/HFLzqbt/Captura-0.jpg" alt="grafana1" title="Grafana" /></p>

<p>Como era de esperar, el sitio web ha cargado correctamente, por lo que podemos concluir que el <em>proxy</em> inverso al puerto 3000 ha funcionado como debería.</p>

<p>De otro lado, vamos a comenzar con la configuración de Grafana, de manera que vamos a hacer <em>login</em> con el usuario <strong>admin</strong> y la contraseña <strong>admin</strong>, que son las credenciales asignadas por defecto, pudiendo apreciar tras ello lo siguiente:</p>

<p><img src="https://i.ibb.co/zZbntQ8/Captura-1.jpg" alt="grafana2" title="Grafana" /></p>

<p>En el siguiente paso, se nos ha ofrecido la posibilidad de cambiar la contraseña del usuario <strong>admin</strong>, que es algo realmente recomendable, pues la contraseña asignada por defecto carece totalmente de seguridad. Tras realizar dicho cambio, accederemos al menú principal, que tendrá la siguiente forma:</p>

<p><img src="https://i.ibb.co/RYpmcNB/Captura-2.jpg" alt="grafana3" title="Grafana" /></p>

<p>Actualmente contamos con un panel de administración web totalmente virgen, pues no tiene configurada ninguna fuente de datos ni mucho menos, gráficas para visualizar las métricas recolectadas. Es por ello que tendremos que pulsar en <strong>Add your first data source</strong>, para así vincular nuestra base de datos con Grafana:</p>

<p><img src="https://i.ibb.co/zXSqLr0/Captura-4.jpg" alt="grafana4" title="Grafana" /></p>

<p>Tras seleccionar <strong>InfluxDB</strong> como fuente de datos, tendremos que realizar la configuración básica para la conexión con dicho gestor:</p>

<ul>
  <li><strong>URL</strong>: Introducimos la dirección y el puerto en el que se encuentra escuchando peticiones. En este caso, al estar ejecutando toda la pila en la misma máquina, será <strong>http://localhost:8086</strong>.</li>
  <li><strong>Database</strong>: Introducimos el nombre de la base de datos. En este caso, <strong>telegraf</strong>.</li>
  <li><strong>User</strong>: Introducimos el nombre del usuario creado. En este caso, <strong>telegraf</strong>.</li>
  <li><strong>Password</strong>: Introducimos la contraseña asociada al usuario especificado.</li>
</ul>

<p>Por último, comprobaremos la conexión con la base de datos y añadiremos dicha fuente de datos pulsando en <strong>Save &amp; Test</strong>:</p>

<p><img src="https://i.ibb.co/Xtgbg0Q/Captura-6.jpg" alt="grafana5" title="Grafana" /></p>

<p>Como se puede apreciar, la conexión con la fuente de datos ha sido exitosa, de manera que ya habremos realizado el paso más importante en cuanto a la configuración de Grafana. Tras ello, tendremos que regresar una vez más al menú principal, pudiendo apreciar lo siguiente:</p>

<p><img src="https://i.ibb.co/FJyGTv1/Captura-8.jpg" alt="grafana6" title="Grafana" /></p>

<p>Una vez que tengamos la posibilidad de acceder a las métricas recolectadas, podremos proceder con la creación de un nuevo <em>dashboard</em> en el que vamos a crear las gráficas con aquellos datos de métricas que consideremos oportunos, pulsando para ello en <strong>Create your first dashboard</strong>:</p>

<p><img src="https://i.ibb.co/0cMHBDy/Captura-9.jpg" alt="grafana7" title="Grafana" /></p>

<p>Los <em>dashboards</em> de Grafana se componen de paneles, representando idealmente cada uno de ellos los datos de una máquina distinta, de manera que vamos a comenzar por configurar el correspondiente a la <strong>VPS</strong>, pulsando para ello en <strong>Add new panel</strong>:</p>

<p><img src="https://i.ibb.co/T23WzJM/Captura-10.jpg" alt="grafana8" title="Grafana" /></p>

<p>Dentro del panel tendremos que definir consultas a la base de datos para así mostrar la información que consideremos oportuna. En este caso, he especificado las siguientes directivas en la consulta:</p>

<ul>
  <li><strong>FROM</strong>: Indicamos la tabla origen de los datos. En este caso, la tabla <strong>mem</strong>, que como se puede suponer, almacena los datos de métricas de la memoria RAM.</li>
  <li><strong>WHERE</strong>: Indicamos una condición para mostrar los datos. En este caso, filtramos los resultados únicamente para el <em>host</em> de nombre <strong>vps</strong>.</li>
  <li><strong>SELECT</strong>: Indicamos la columna de la tabla que deseamos mostrar. En este caso, el porcentaje de uso, que se encuentra referenciado en la columna <strong>used_percent</strong>.</li>
</ul>

<p>Como se puede apreciar, la gráfica se genera automáticamente a partir de la consulta realizada, de manera que tras añadir varias consultas para mostrar información sobre determinados aspectos de dicha máquina, así como configurar los paneles para el resto de máquinas, guardaremos el <em>dashboard</em>, asignándole como es lógico un nombre identificativo. Este sería el resultado final:</p>

<p><img src="https://i.ibb.co/DbJ7Yjk/Captura-20.jpg" alt="grafana9" title="Grafana" /></p>

<p>Tras apreciar la apariencia gráfica de las métricas recolectadas en todas las máquinas existentes en mi infraestructura, podemos concluir que la pila <strong>InfluxDB + Telegraf + Grafana</strong> es realmente completa y potente, pues permite centralizar todas las métricas que deseemos y tenerlo todo controlado de una manera realmente sencilla.</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="sistemas" /><category term="openstack" /><summary type="html"><![CDATA[El objetivo de esta tarea es el de continuar con la configuración del escenario de trabajo previamente generado en OpenStack, concretamente, llevando a cabo una instalación de un sistema de recolección de métricas sobre el mismo, incluyendo a su vez, las métricas de la máquina VPS contratada en OVH que en otros artículos hemos tratado. Dicho sistema nos permitirá recolectar, centralizar y filtrar determinados aspectos sobre el rendimiento de las máquinas para su posterior representación gráfica que permita controlar la evolución temporal de parámetros esenciales de todos los servidores, permitiendo por tanto detectar anomalías en los mismos. El sistema de recolección de métricas que vamos a instalar es una pila compuesta por los siguientes elementos: InfluxDB: Base de datos basada en series de tiempo (time-series database), pensada para almacenar y tratar de la manera más eficiente posible una gran cantidad de información por segundo, permitiendo a su vez realizar análisis de dichos datos en tiempo real (medias, mínimos, máximos, búsquedas en el tiempo…). Telegraf: Servicio que recopila y envía datos de métricas de diferentes sistemas, como por ejemplo uso de disco, carga del sistema, uso de la memoria RAM, carga de la CPU… La salida de Telegraf, por norma general, se envía a una base de datos de tipo series de tiempo como InfluxDB. Cuenta con una enorme lista de plugins para aumentar sus funcionalidades. Grafana: Servicio que permite crear cuadros de mando y gráficos a partir de múltiples fuentes, incluidas las bases de datos de series de tiempo como InfluxDB. Permite la creación de usuarios con diferentes privilegios dentro de la aplicación, por lo que es posible compartir con otros miembros los paneles generados. Sin embargo, en este caso contamos con dos escenarios existentes, uno privado y uno público: OpenStack: Escenario privado en el que se encuentran ubicadas las máquinas Dulcinea (Debian 10), Sancho (Ubuntu 20.04), Quijote (CentOS 8) y Freston (Debian 10), que como se puede suponer, cuentan con direccionamiento privado. OVH: Escenario público en el que se encuentra ubicada la VPS (Debian 10), que como se puede suponer, cuenta con direccionamiento público. Para entender el problema existente, es necesario recalcar una vez más cómo funciona Telegraf. Dicho servicio recolecta las métricas especificadas y posteriormente abre una conexión con el servidor configurado que será aquel que esté ejecutando la base de datos en la que se va a almacenar la información. En otras palabras, el cliente Telegraf necesitará alcanzar a la máquina en la que se almacenarán las métricas. Si nuestra intención fuese recolectar métricas únicamente en el escenario privado, no habría problema, sin embargo, la VPS no será capaz de alcanzar las máquinas ubicadas en el escenario OpenStack privado, al contar las mismas con un direccionamiento no alcanzable desde el exterior. Para solventar esta situación, tenemos dos opciones: Instalar InfluxDB en una de las máquinas OpenStack y posteriormente configurar una VPN en la VPS para que se pueda alcanzar dicha máquina desde el exterior. Instalar InfluxDB en la VPS, que será alcanzable desde cualquiera de las máquinas OpenStack sin necesidad de llevar a cabo ninguna configuración adicional. En mi caso, voy a optar por la segunda opción dada la sencillez que me aporta, evitando por tanto el hecho de tener que llevar a cabo configuraciones adicionales. Es por ello que la instalación de InfluxDB, Telegraf y Grafana se llevará a cabo en la VPS, mientras que en el resto de máquinas únicamente será necesario instalar Telegraf para así enviar dichas métricas a la base de datos alojada en la VPS. InfluxDB El primer paso consistirá en instalar la paquetería necesaria en la VPS para poder trabajar con InfluxDB, los cimientos de este sistema, que como ya hemos mencionado, será el gestor de bases de datos que almacenará las métricas recolectadas. Dado que dicho paquete cuenta con una gran diferencia de versión en los repositorios de Debian con respecto a upstream, procederemos a descargarlo de los repositorios oficiales de InfluxData. Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando: root@vps:~# echo "deb https://repos.influxdata.com/debian buster stable" &gt; /etc/apt/sources.list.d/influxdb.list De otro lado, tendremos que añadir también la clave pública GPG de InfluxData a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de InfluxDB que vamos a descargar, haciendo para ello uso del comando: root@vps:~# wget -qO- https://repos.influxdata.com/influxdb.key | apt-key add - OK Tras ello, podremos dar paso a la instalación de InfluxDB, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando: root@vps:~# apt update &amp;&amp; apt install influxdb El paquete influxdb ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando: root@vps:~# systemctl status influxdb ● influxdb.service - InfluxDB is an open-source, distributed, time series database Loaded: loaded (/lib/systemd/system/influxdb.service; enabled; vendor preset: enabled) Active: inactive (dead) Docs: https://docs.influxdata.com/influxdb/ Como se puede apreciar, el servicio se encuentra habilitado para arrancar junto a la máquina (enabled), sin embargo, actualmente no se encuentra activo (inactive), de manera que procederemos a levantarlo manualmente, ejecutando para ello la siguiente instrucción: root@vps:~# systemctl start influxdb Si verificamos una vez más el estado del servicio, haciendo para ello uso del comando utilizado con anterioridad, se nos mostrará lo siguiente: root@vps:~# systemctl status influxdb ● influxdb.service - InfluxDB is an open-source, distributed, time series database Loaded: loaded (/lib/systemd/system/influxdb.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2021-02-05 18:28:43 CET; 2s ago Docs: https://docs.influxdata.com/influxdb/ Main PID: 5255 (influxd) Tasks: 7 (limit: 2318) Memory: 13.8M CGroup: /system.slice/influxdb.service └─5255 /usr/bin/influxd -config /etc/influxdb/influxdb.conf Efectivamente, el servicio se encuentra ahora activo y listo para su uso. Como consecuencia del proceso que está actualmente ejecutándose, se habrán abierto dos sockets TCP/IP en los puertos por defecto de InfluxDB (8086 y 8088) que estarán escuchando peticiones, así que para verificarlo haremos uso del comando: root@vps:~# netstat -tlnp | egrep 'influx' tcp 0 0 127.0.0.1:8088 0.0.0.0:* LISTEN 5255/influxd tcp6 0 0 :::8086 :::* LISTEN 5255/influxd -t: Filtramos únicamente para las conexiones que utilizan el protocolo TCP. -l: Filtramos únicamente para los sockets que están actualmente escuchando peticiones (State = LISTEN). -n: Indicamos que muestre las direcciones y puertos de forma numérica, en lugar de intentar traducirlos. -p: Indicamos que muestre el PID y el nombre del proceso al que pertenece dicho socket. Efectivamente, el proceso influxd está escuchando peticiones en todas las interfaces de la máquina en el puerto 8086, que es aquel que se expone al exterior para recibir peticiones HTTP y del que por tanto, haremos uso para recibir las métricas del resto de máquinas. De otro lado, el puerto 8088 se encuenta limitado a la interfaz loopback de la máquina, ya que es el que se utiliza para interactuar de forma directa con la base de datos en operaciones como copias de seguridad y restauraciones, que no debe ser accesible desde el exterior. Tras ello, procederemos a abrir una shell de influx para así poder gestionar el motor, ejecutando para ello el comando: root@vps:~# influx Connected to http://localhost:8086 version 1.8.4 InfluxDB shell version: 1.8.4 Como se puede apreciar, la influx shell se ha abierto correctamente y está lista para su uso. Lo primero que haremos estando dentro del servidor de bases de datos será crear una nueva base de datos en la que almacenar las métricas, valga la redundancia. En este caso, de nombre telegraf. Para ello, haremos uso de la instrucción: &gt; CREATE DATABASE telegraf La base de datos en cuestión habrá sido generada, pero para verificarlo, vamos a listar todas las bases de datos existentes, ejecutando para ello la instrucción: &gt; SHOW DATABASES name: databases name ---- _internal telegraf Efectivamente, la base de datos telegraf ha sido correctamente generada, sin embargo, de nada nos sirve tener una base de datos si no tenemos un usuario que pueda escribir en la misma. Únicamente necesitamos un usuario, ya que la distinción entre las máquinas que envían sus métricas se hace mediante la columna host del registro almacenado, que como se puede suponer, contendrá el nombre de la máquina que ha enviado el registro. En este caso, vamos a crear un usuario de nombre “telegraf” cuya contraseña, como es lógico, censuraré por seguridad. Para ello, haremos uso de la instrucción: &gt; CREATE USER telegraf WITH PASSWORD 'XXXXXXXXXX' El usuario en cuestión habrá sido generado, pero para verificarlo, vamos a listar todos los usuarios existentes, ejecutando para ello la instrucción: &gt; SHOW USERS user admin ---- ----- telegraf false Efectivamente, el usuario telegraf ha sido correctamente generado. Una vez realizadas las oportunas modificaciones, podremos salir de influx haciendo uso de la instrucción exit, con la finalidad de continuar ahora con la instalación de Telegraf. Telegraf VPS El segundo paso consistirá en instalar la paquetería necesaria en todas las máquinas para poder recolectar nuestras métricas gracias a Telegraf y enviarlas a la base de datos previamente generada. Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de InfluxData. Como podemos apreciar, dicho software es de la misma organización que InfluxDB, de manera que nos servirá la anterior clave pública GPG y repositorio instalados para realizar la descarga e instalación del paquete, haciendo para ello uso del comando: root@vps:~# apt install telegraf El paquete telegraf ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando: root@vps:~# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/lib/systemd/system/telegraf.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2021-02-05 18:35:08 CET; 3s ago Docs: https://github.com/influxdata/telegraf Main PID: 5480 (telegraf) Tasks: 6 (limit: 2318) Memory: 19.6M CGroup: /system.slice/telegraf.service └─5480 /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d Efectivamente, el servicio se encuentra activo y listo para su uso, sin embargo, no se encuentra configurado para actuar de la manera que nosotros necesitamos. Para solucionarlo, procederemos a modificar el fichero /etc/telegraf/telegraf.conf, en el que estableceremos los parámetros deseados, como por ejemplo la base de datos remota, el nombre de la máquina… ejecutando para ello el comando: root@vps:~# nano /etc/telegraf/telegraf.conf Dentro del mismo, podremos encontrar las siguientes secciones: Output Plugins: Envía las métricas recolectadas a diferentes destinos, en mi caso a InfluxDB. Processor Plugins: Transforma, decora y filtra las métricas recolectadas. Aggregator Plugins: Crea conjuntos de métricas, por ejemplo, permitiendo hacer una media, mínimo, máximo… Input Plugins: Recolecta las métricas especificadas del sistema o servicios. En este caso, tendremos que buscar la sección de nombre [[outputs.influxdb]] dentro de Output Plugins para indicar dentro de la misma los parámetros necesarios para llevar a cabo la conexión con InfluxDB. El primer parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto: # urls = ["http://127.0.0.1:8086"] Como se puede suponer, tendremos que descomentarlo e indicar la dirección y el puerto en la que se aloja la base de datos InfluxDB, que en este caso es correcta, pues se está ejecutando en localhost en el puerto 8086. En el resto de máquinas, tendremos que introducir la dirección IP pública de dicha VPS, es decir, 51.210.109.246. Por tanto, el resultado final de dicha directiva sería: urls = ["http://127.0.0.1:8086"] El segundo parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto: # database = "telegraf" Como se puede suponer, tendremos que descomentarlo e indicar el nombre de la base de datos InfluxDB que previamente hemos generado, que una vez más, es correcto. Por tanto, el resultado final de dicha directiva sería: database = "telegraf" El tercer parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto: # skip_database_creation = false Como se puede suponer, tendremos que descomentarlo y asignarle el valor true para que así no trate de generar la base de datos InfluxDB, ya que previamente la hemos generado nosotros manualmente. Por tanto, el resultado final de dicha directiva sería: skip_database_creation = true El cuarto parámetro que modificaremos dentro de dicha sección será el que por defecto tiene el siguiente aspecto: # timeout = "5s" Como se puede suponer, tendremos que descomentarlo e indicar el valor de tiempo que queremos que transcurra antes de que la petición HTTP a la base de datos destino expire en caso de no haber sido alcanzada, que en este caso, 5 segundos es un valor que me parece correcto. Por tanto, el resultado final de dicha directiva sería: timeout = "5s" Por último, el quinto y sexto parámetro que modificaremos dentro de dicha sección serán los que por defecto tienen el siguiente aspecto: # username = "telegraf" # password = "metricsmetricsmetricsmetrics" Como se puede suponer, tendremos que descomentarlos e indicar el nombre del usuario que previamente hemos generado en InfluxDB, así como la contraseña del mismo, que censuraré por seguridad. Por tanto, el resultado final de dichas directivas sería: username = "telegraf" password = "XXXXXXXXXX" Listo, la configuración básica del cliente Telegraf ha finalizado. Es importante mencionar que Telegraf es un software realmente grande y nos ofrece una cantidad descomunal de posibilidades de recolección de métricas (plugins). Tratar todas ellas en un artículo sería totalmente inviable, de manera que aquellas que hemos modificado son las justas y necesarias para el correcto funcionamiento básico del mismo, ya que se incluyen las más esenciales habilitadas por defecto (uso de disco, carga del sistema, uso de la memoria RAM, carga de la CPU…). Otros dos parámetros muy importantes se encuentran en la sección [agent], siendo los siguientes: interval: Frecuencia con la que queremos que se recolecten las métricas, que por defecto son 10 segundos. Mientras menor sea dicho valor, más exacto será nuestro sistema de recolección de métricas, pero a su vez, mayor será el flujo de datos que viajará entre las máquinas. hostname: En caso de no especificar un hostname en concreto, se utilizará aquel que la máquina tiene asignado, que como se puede suponer, tiene la finalidad de identificar de forma única a cada máquina dentro del sistema de recolección de métricas. Tras ello, guardaremos los cambios y dado que hemos modificado el fichero de configuración de un servicio, tendremos que reiniciarlo para así cargar la nueva configuración en memoria, haciendo para ello uso del comando: root@sancho:~# systemctl restart telegraf Dado que el proceso de instalación de Telegraf es prácticamente igual en todas las máquinas, vamos a hacer un pequeño resumen para no repetir la misma explicación para cada una de ellas: Añadimos la clave pública GPG y el correspondiente repositorio de InfluxData. Descargamos e instalamos el paquete telegraf, actualizando previamente la lista de la paquetería disponible. Comprobamos que el servicio se encuentra activo y habilitado durante el arranque. Llevamos a cabo las modificaciones previamente mencionadas en el fichero /etc/telegraf/telegraf.conf. Reiniciamos el servicio para cargar la nueva configuración en memoria. Dulcinea root@dulcinea:~# wget -qO- https://repos.influxdata.com/influxdb.key | apt-key add - OK root@dulcinea:~# echo "deb https://repos.influxdata.com/debian buster stable" &gt; /etc/apt/sources.list.d/influxdb.list root@dulcinea:~# apt update &amp;&amp; apt install telegraf root@dulcinea:~# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/lib/systemd/system/telegraf.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2021-02-06 11:06:39 CET; 4s ago Docs: https://github.com/influxdata/telegraf Main PID: 16138 (telegraf) Tasks: 7 (limit: 562) Memory: 13.7M CGroup: /system.slice/telegraf.service └─16138 /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d root@dulcinea:~# nano /etc/telegraf/telegraf.conf # urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"] # database = "telegraf" -&gt; database = "telegraf" # skip_database_creation = false -&gt; skip_database_creation = true # timeout = "5s" -&gt; timeout = "5s" # username = "telegraf" -&gt; username = "telegraf" # password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX" root@dulcinea:~# systemctl restart telegraf Sancho root@sancho:~# wget -qO- https://repos.influxdata.com/influxdb.key | apt-key add - OK root@sancho:~# echo "deb https://repos.influxdata.com/ubuntu focal stable" &gt; /etc/apt/sources.list.d/influxdb.list root@sancho:~# apt update &amp;&amp; apt install telegraf root@sancho:~# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/lib/systemd/system/telegraf.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2021-02-06 11:14:38 CET; 24s ago Docs: https://github.com/influxdata/telegraf Main PID: 18572 (telegraf) Tasks: 7 (limit: 533) Memory: 42.9M CGroup: /system.slice/telegraf.service └─18572 /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d root@sancho:~# nano /etc/telegraf/telegraf.conf # urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"] # database = "telegraf" -&gt; database = "telegraf" # skip_database_creation = false -&gt; skip_database_creation = true # timeout = "5s" -&gt; timeout = "5s" # username = "telegraf" -&gt; username = "telegraf" # password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX" root@sancho:~# systemctl restart telegraf Quijote [root@quijote ~]# vi /etc/yum.repos.d/influxdb.repo [influxdb] name = InfluxDB Repository - RHEL baseurl = https://repos.influxdata.com/rhel/7/x86_64/stable/ enabled = 1 gpgcheck = 1 gpgkey = https://repos.influxdata.com/influxdb.key [root@quijote ~]# dnf install telegraf [root@quijote ~]# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/usr/lib/systemd/system/telegraf.service; enabled; vendor preset: disabled) Active: inactive (dead) Docs: https://github.com/influxdata/telegraf [root@quijote ~]# systemctl start telegraf [root@quijote ~]# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/usr/lib/systemd/system/telegraf.service; enabled; vendor preset: disabled) Active: active (running) since Sat 2021-02-06 11:24:15 CET; 1s ago Docs: https://github.com/influxdata/telegraf Main PID: 5932 (telegraf) Tasks: 4 (limit: 2635) Memory: 52.4M CGroup: /system.slice/telegraf.service └─5932 /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d [root@quijote ~]# vi /etc/telegraf/telegraf.conf # urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"] # database = "telegraf" -&gt; database = "telegraf" # skip_database_creation = false -&gt; skip_database_creation = true # timeout = "5s" -&gt; timeout = "5s" # username = "telegraf" -&gt; username = "telegraf" # password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX" [root@quijote ~]# systemctl restart telegraf Freston root@freston:~# wget -qO- https://repos.influxdata.com/influxdb.key | apt-key add - OK root@freston:~# echo "deb https://repos.influxdata.com/debian buster stable" &gt; /etc/apt/sources.list.d/influxdb.list root@freston:~# apt update &amp;&amp; apt install telegraf root@freston:~# systemctl status telegraf ● telegraf.service - The plugin-driven server agent for reporting metrics into InfluxDB Loaded: loaded (/lib/systemd/system/telegraf.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2021-02-06 11:27:47 CET; 5s ago Docs: https://github.com/influxdata/telegraf Main PID: 15462 (telegraf) Tasks: 7 (limit: 562) Memory: 17.0M CGroup: /system.slice/telegraf.service └─15462 /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d root@freston:~# nano /etc/telegraf/telegraf.conf # urls = ["http://127.0.0.1:8086"] -&gt; urls = ["http://51.210.109.246:8086"] # database = "telegraf" -&gt; database = "telegraf" # skip_database_creation = false -&gt; skip_database_creation = true # timeout = "5s" -&gt; timeout = "5s" # username = "telegraf" -&gt; username = "telegraf" # password = "metricsmetricsmetricsmetrics" -&gt; password = "XXXXXXXXXX" root@freston:~# systemctl restart telegraf Grafana El tercer y último paso consistirá en instalar la paquetería necesaria en la VPS para poder visualizar de forma gráfica las métricas recolectadas en la base de datos gracias a Grafana y tratar las mismas como nos sea necesario. Dado que dicho paquete no se encuentra actualmente disponible en los repositorios de Debian, procederemos a descargarlo de los repositorios oficiales de Grafana. Para añadir dicho repositorio a nuestra máquina, ejecutaremos el comando: root@vps:~# echo "deb https://packages.grafana.com/enterprise/deb stable main" &gt; /etc/apt/sources.list.d/grafana.list De otro lado, tendremos que añadir también la clave pública GPG de Grafana a nuestro anillo de claves para así poder verificar la integridad e instalar el paquete de Grafana que vamos a descargar, haciendo para ello uso del comando: root@vps:~# wget -q -O - https://packages.grafana.com/gpg.key | apt-key add - OK Tras ello, podremos dar paso a la instalación de Grafana, no sin antes actualizar la lista de la paquetería disponible, ejecutando para ello el comando: root@vps:~# apt update &amp;&amp; apt install grafana El paquete grafana ha sido instalado en la máquina, de manera que vamos a verificar el estado de dicho servicio para así comprobar que se encuentra actualmente en ejecución, haciendo para ello uso del comando: root@vps:~# systemctl status grafana-server ● grafana-server.service - Grafana instance Loaded: loaded (/lib/systemd/system/grafana-server.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: http://docs.grafana.org Como se puede apreciar, el servicio no se encuentra habilitado para arrancar junto a la máquina (disabled), además, tampoco se encuentra activo (inactive), de manera que procederemos a habilitarlo y levantarlo manualmente, ejecutando para ello las siguientes instrucciones: root@vps:~# systemctl start grafana-server root@vps:~# systemctl enable grafana-server Si verificamos una vez más el estado del servicio, haciendo para ello uso del comando utilizado con anterioridad, se nos mostrará lo siguiente: root@vps:~# systemctl status grafana-server ● grafana-server.service - Grafana instance Loaded: loaded (/lib/systemd/system/grafana-server.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2021-02-05 18:08:19 CET; 22s ago Docs: http://docs.grafana.org Main PID: 3994 (grafana-server) Tasks: 7 (limit: 2318) Memory: 27.2M CGroup: /system.slice/grafana-server.service └─3994 /usr/sbin/grafana-server --config=/etc/grafana/grafana.ini --pidfile=/var/run/grafana/grafana-server.pid --packaging=deb cfg:default.paths.logs=/var/log/grafana cfg:defaul Efectivamente, el servicio se encuentra ahora habilitado, activo y listo para su uso. Como consecuencia del proceso que está actualmente ejecutándose, se habrá abierto un socket TCP/IP en el puerto por defecto de Grafana (3000) que estará escuchando peticiones provenientes de todas las interfaces de la máquina, así que para verificarlo haremos uso del comando: root@vps:~# netstat -tlnp | egrep 'grafana' tcp6 0 0 :::3000 :::* LISTEN 3994/grafana-server Efectivamente, el proceso grafana-server está escuchando peticiones en todas las interfaces de la máquina en el puerto 3000, que es aquel que se expone al exterior para poder acceder al panel de administración web acondicionado para ello. Antes de continuar, es importante mencionar que la configuración por defecto de Grafana es poco restrictiva, pues permite a los usuarios registrarse de forma manual, que en mi caso no es lo que busco. Para evitarlo, tendremos que modificar el fichero de nombre /etc/grafana/grafana.ini, ejecutando para ello el comando: root@vps:~# nano /etc/grafana/grafana.ini Dentro del mismo, tendremos que modificar los siguientes parámetros para deshabilitar el registro de usuarios y la creación de organizaciones: ;allow_sign_up = true -&gt; allow_sign_up = false ;allow_org_create = true -&gt; allow_org_create = false Tras ello, guardaremos los cambios y dado que hemos modificado el fichero de configuración de un servicio, tendremos que reiniciarlo para así cargar la nueva configuración en memoria, haciendo para ello uso del comando: root@vps:~# systemctl restart grafana-server Como anteriormente hemos mencionado, el servicio grafana-server se encuentra en ejecución en el puerto 3000, lo que supone que cada vez que queramos acceder al panel de administración web tendremos que conectarnos a dicho puerto de forma explícita (http://51.210.109.246:3000), que es algo que puede resultar un tanto incómodo. Para solventar este “problema”, he decidido hacer uso del servidor web nginx alojado en dicha máquina que se encuentra actualmente escuchando peticiones en los puertos 80 (HTTP) y 443 (HTTPS), de manera que crearemos un VirtualHost accesible desde un determinado nombre de dominio y reenviaremos la petición entrante a través de un proxy inverso al puerto 3000, consiguiendo por tanto utilizar los puertos HTTP y HTTPS para hacer uso del panel de administración web de Grafana. El primer paso ha consistido en la generación de un nuevo nombre dentro de la zona DNS iesgn19.es, concretamente nombrando un nuevo servicio metricas mediante un registro CNAME al registro A que apunta a la dirección 51.210.109.246, que será a través del cuál accederemos al VirtualHost en cuestión, tal y como podemos apreciar a continuación: Como bien sabemos, si queremos hacer uso del protocolo HTTPS para dicho nombre de dominio, necesitaremos generar un certificado firmado por una autoridad certificadora de considerable reputación, así que en mi caso, he recurrido una vez más a Let’s Encrypt, siguiendo para ello los pasos indicados en el artículo Configuración de HTTPS en Nginx, pues mencionar aquí el proceso se saldría completamente del objetivo del post. Cuando nuestro certificado haya sido correctamente generado, todo estará listo para configurar el nuevo VirtualHost dentro del directorio /etc/nginx/sites-available/, cuyo nombre he decidido que en este caso sea metricas, de manera que ejecutaremos el comando: root@vps:~# nano /etc/nginx/sites-available/metricas Dentro del mismo, tendremos que crear dos directivas server, una para el VirtualHost accesible en el puerto 80 HTTP, dentro del cuál únicamente asignaremos el ServerName correspondiente y una redirección permanente para así forzar HTTPS. En la otra, para el VirtualHost accesible en el puerto 443 HTTPS, tendremos que configurar las siguientes directivas: server_name: Indicaremos el nombre de dominio a través del cuál accederemos al servidor. ssl: Activa el motor SSL, necesario para hacer uso de HTTPS, por lo que su valor debe ser on. ssl_certificate: Indicamos la ruta del certificado del servidor firmado por la CA. En este caso, /etc/letsencrypt/live/metricas.iesgn19.es/fullchain.pem. ssl_certificate_key: Indicamos la ruta de la clave privada asociada al certificado del servidor. En este caso, /etc/letsencrypt/live/metricas.iesgn19.es/privkey.pem. location: Especificamos las instrucciones para resolver una petición a la ruta introducida (en este caso, cualquier ruta, ya que hemos indicado la raíz y se aplica de forma recursiva a partir de la misma). Dentro de dicho bloque, definimos mediante una directiva proxy_pass a qué dirección se debe reenviar la petición, que en este caso, será al puerto 3000 de la máquina local. El resultado final sería: server { listen 80; listen [::]:80; server_name metricas.iesgn19.es; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name metricas.iesgn19.es; ssl on; ssl_certificate /etc/letsencrypt/live/metricas.iesgn19.es/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/metricas.iesgn19.es/privkey.pem; location / { proxy_pass http://localhost:3000; } } Listo, toda la configuración necesaria ya ha sido realizada, así que únicamente queda habilitar dicho sitio y probar que realmente funciona. Para activar el sitio, a diferencia de apache2 que contaba con una utilidad para ello, tendremos crear el enlace simbólico al fichero de configuración ubicado en /etc/nginx/sites-available/ dentro de /etc/nginx/sites-enabled/ de forma manual. Para ello, haremos uso del comando: root@vps:~# ln -s /etc/nginx/sites-available/metricas /etc/nginx/sites-enabled/ Al parecer, el sitio ha sido correctamente habilitado, pero para activar la nueva configuración, tendremos que volver a cargar la configuración del servicio nginx, ejecutando para ello el comando: root@vps:~# systemctl reload nginx Una vez que la configuración del servicio se ha vuelto a cargar, vamos a listar el contenido de /etc/nginx/sites-enabled/ para verificar que el correspondiente enlace simbólico ha sido correctamente creado. Para ello, haremos uso del comando: root@vps:~# ls -l /etc/nginx/sites-enabled/ total 0 lrwxrwxrwx 1 root root 34 Nov 3 16:11 default -&gt; /etc/nginx/sites-available/default lrwxrwxrwx 1 root root 33 Nov 11 11:21 drupal -&gt; /etc/nginx/sites-available/drupal lrwxrwxrwx 1 root root 34 Nov 3 17:14 iesgn19 -&gt; /etc/nginx/sites-available/iesgn19 lrwxrwxrwx 1 root root 35 Feb 5 19:37 metricas -&gt; /etc/nginx/sites-available/metricas Efectivamente, el nuevo sitio se encuentra actualmente activo, así que es hora de realizar la correspondiente prueba de acceso. Para ello, abriremos un navegador web y trataremos de acceder a https://metricas.iesgn19.es: Como era de esperar, el sitio web ha cargado correctamente, por lo que podemos concluir que el proxy inverso al puerto 3000 ha funcionado como debería. De otro lado, vamos a comenzar con la configuración de Grafana, de manera que vamos a hacer login con el usuario admin y la contraseña admin, que son las credenciales asignadas por defecto, pudiendo apreciar tras ello lo siguiente: En el siguiente paso, se nos ha ofrecido la posibilidad de cambiar la contraseña del usuario admin, que es algo realmente recomendable, pues la contraseña asignada por defecto carece totalmente de seguridad. Tras realizar dicho cambio, accederemos al menú principal, que tendrá la siguiente forma: Actualmente contamos con un panel de administración web totalmente virgen, pues no tiene configurada ninguna fuente de datos ni mucho menos, gráficas para visualizar las métricas recolectadas. Es por ello que tendremos que pulsar en Add your first data source, para así vincular nuestra base de datos con Grafana: Tras seleccionar InfluxDB como fuente de datos, tendremos que realizar la configuración básica para la conexión con dicho gestor: URL: Introducimos la dirección y el puerto en el que se encuentra escuchando peticiones. En este caso, al estar ejecutando toda la pila en la misma máquina, será http://localhost:8086. Database: Introducimos el nombre de la base de datos. En este caso, telegraf. User: Introducimos el nombre del usuario creado. En este caso, telegraf. Password: Introducimos la contraseña asociada al usuario especificado. Por último, comprobaremos la conexión con la base de datos y añadiremos dicha fuente de datos pulsando en Save &amp; Test: Como se puede apreciar, la conexión con la fuente de datos ha sido exitosa, de manera que ya habremos realizado el paso más importante en cuanto a la configuración de Grafana. Tras ello, tendremos que regresar una vez más al menú principal, pudiendo apreciar lo siguiente: Una vez que tengamos la posibilidad de acceder a las métricas recolectadas, podremos proceder con la creación de un nuevo dashboard en el que vamos a crear las gráficas con aquellos datos de métricas que consideremos oportunos, pulsando para ello en Create your first dashboard: Los dashboards de Grafana se componen de paneles, representando idealmente cada uno de ellos los datos de una máquina distinta, de manera que vamos a comenzar por configurar el correspondiente a la VPS, pulsando para ello en Add new panel: Dentro del panel tendremos que definir consultas a la base de datos para así mostrar la información que consideremos oportuna. En este caso, he especificado las siguientes directivas en la consulta: FROM: Indicamos la tabla origen de los datos. En este caso, la tabla mem, que como se puede suponer, almacena los datos de métricas de la memoria RAM. WHERE: Indicamos una condición para mostrar los datos. En este caso, filtramos los resultados únicamente para el host de nombre vps. SELECT: Indicamos la columna de la tabla que deseamos mostrar. En este caso, el porcentaje de uso, que se encuentra referenciado en la columna used_percent. Como se puede apreciar, la gráfica se genera automáticamente a partir de la consulta realizada, de manera que tras añadir varias consultas para mostrar información sobre determinados aspectos de dicha máquina, así como configurar los paneles para el resto de máquinas, guardaremos el dashboard, asignándole como es lógico un nombre identificativo. Este sería el resultado final: Tras apreciar la apariencia gráfica de las métricas recolectadas en todas las máquinas existentes en mi infraestructura, podemos concluir que la pila InfluxDB + Telegraf + Grafana es realmente completa y potente, pues permite centralizar todas las métricas que deseemos y tenerlo todo controlado de una manera realmente sencilla.]]></summary></entry><entry><title type="html">Interconexión de PostgreSQL y Oracle 19c</title><link href="https://www.alvarovf.com/bbdd/2021/02/06/interconexion-oracle-postgresql.html" rel="alternate" type="text/html" title="Interconexión de PostgreSQL y Oracle 19c" /><published>2021-02-06T18:03:00+00:00</published><updated>2021-02-06T18:03:00+00:00</updated><id>https://www.alvarovf.com/bbdd/2021/02/06/interconexion-oracle-postgresql</id><content type="html" xml:base="https://www.alvarovf.com/bbdd/2021/02/06/interconexion-oracle-postgresql.html"><![CDATA[<p>El objetivo de este <em>post</em> es el de mostrar el procedimiento a seguir para llevar a cabo una interconexión entre dos servidores <strong>Oracle 19c</strong> y <strong>PostgreSQL</strong> alojados en máquinas <strong>CentOS 8</strong> y <strong>Debian Buster</strong>, respectivamente, siendo su finalidad la de permitir el acceso desde un mismo cliente a dos bases de datos ubicadas en gestores completamente distintos, de una forma simultánea pero indirecta, de manera que uno de los servidores actuará como cliente del otro servidor, de manera unidireccional.</p>

<p>Al fin y al cabo, el cliente únicamente abre una conexión, pues es el primer servidor al que se conecta el que posteriormente abre una conexión al segundo de ellos.</p>

<p>Para esta ocasión, voy a reutilizar dos de las máquinas virtuales creadas en los artículos <a href="https://www.alvarovf.com/bbdd/2021/02/04/interconexion-oracle-centos.html">Interconexión de Oracle 19c en CentOS 8</a> e <a href="https://www.alvarovf.com/bbdd/2021/02/05/instalacion-interconexion-postgresql-debian.html">Instalación e interconexión de PostgreSQL en Debian 10</a>, por lo que se recomienda la previa lectura de dichos <em>posts</em>, pues en este artículo se tratarán con menor profundidad u obviarán algunos aspectos vistos con anterioridad. Las máquinas actualmente existentes en el escenario son las siguientes:</p>

<ul>
  <li><strong>oracle1</strong>: Máquina <strong>CentOS 8</strong> con <strong>Oracle 19c</strong>, conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.150</strong>.</li>
  <li><strong>servidor2</strong>: Máquina <strong>Debian Buster</strong> con <strong>PostgreSQL</strong>, conectada a mi red doméstica en modo puente (<em>bridge</em>), con dirección IP asignada <strong>192.168.1.161</strong>.</li>
</ul>

<h2 id="oracle-19c-a-postgresql">Oracle 19c a PostgreSQL</h2>

<p>Partimos del punto en el que tanto el servidor <strong>Oracle 19c</strong> como el <strong>PostgreSQL</strong> están totalmente operativos y listos para escuchar peticiones remotas. Como en anteriores artículos hemos visto, los servidores Oracle cuentan con soporte nativo para realizar enlaces con otros servidores Oracle, sin embargo, no ocurre lo mismo cuando deseamos conectarnos a otro gestor totalmente diferente, como puede ser PostgreSQL.</p>

<p>En estos casos, podemos acudir a <strong>ODBC</strong> (<em>Open Database Connectivity</em>), un estándar de acceso a las bases de datos cuyo objetivo es hacer posible el acceso a cualquier dato desde cualquier aplicación, sin importar qué sistema de gestión de bases de datos almacene los datos. En este caso, vamos a configurar un enlace a una base de datos <strong>PostgreSQL</strong>, sin embargo, es posible realizar una integración con muchos otros gestores como <strong>MySQL</strong> e incluso gestores no relacionales como <strong>Excel</strong>.</p>

<p><img src="https://i.ibb.co/7WDGktg/dblink-1.png" alt="diagrama1" title="Diagrama enlace" /></p>

<p>El primer paso consistirá en instalar la paquetería necesaria para crear dicho enlace, concretamente el paquete <strong>unixODBC</strong>, que es el proyecto de código abierto que implementa la API <strong>ODBC</strong> de la que previamente hemos hablado.</p>

<p>De otro lado, dado que pretendemos crear un enlace con un servidor <strong>PostgreSQL</strong>, tendremos que instalar el <em>driver</em> específico para ello, de nombre <strong>postgresql-odbc</strong>, no sin antes asegurar que toda la paquetería instalada en la máquina se encuentra en su última versión, por lo que ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# dnf update <span class="o">&amp;&amp;</span> dnf <span class="nb">install </span>unixODBC postgresql-odbc</code></pre></figure>

<p>Cuando la instalación de los paquetes haya finalizado, procederemos a modificar el fichero <strong>/etc/odbcinst.ini</strong>, en el que encontraremos la configuración de todos los <em>drivers</em> de <strong>ODBC</strong> existentes, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# nano /etc/odbcinst.ini</code></pre></figure>

<p>De todos los <em>drivers</em> existentes, el único que nos interesa es el referente a <strong>PostgreSQL</strong>, que tendrá la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>PostgreSQL]
Description     <span class="o">=</span> ODBC <span class="k">for </span>PostgreSQL
Driver          <span class="o">=</span> /usr/lib/psqlodbcw.so
Setup           <span class="o">=</span> /usr/lib/libodbcpsqlS.so
Driver64        <span class="o">=</span> /usr/lib64/psqlodbcw.so
Setup64         <span class="o">=</span> /usr/lib64/libodbcpsqlS.so
FileUsage	<span class="o">=</span> 1</code></pre></figure>

<p>Como se puede apreciar, la definición del mismo se encuentra compuesta por una breve descripción y las rutas a las diferentes librerías que lo constituyen. En mi caso, he comentado la definición del resto de <em>drivers</em>, ya que para este caso no me van a ser necesarios.</p>

<p>El siguiente paso consistirá en crear un <strong>DSN</strong> (<em>Data Source Name</em>) en el fichero <strong>/etc/odbc.ini</strong>, pues dicho fichero será posteriormente utilizado para determinar la manera de conectarse al gestor especificado. Para ello, ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# nano /etc/odbc.ini</code></pre></figure>

<p>Dentro del mismo, introduciremos el siguiente contenido:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>PSQLU]
Debug           <span class="o">=</span> 0
CommLog         <span class="o">=</span> 0
ReadOnly        <span class="o">=</span> 0
Driver          <span class="o">=</span> PostgreSQL
Servername      <span class="o">=</span> 192.168.1.161
Username        <span class="o">=</span> alvaro2
Password        <span class="o">=</span> alvaro2
Port            <span class="o">=</span> 5432
Database        <span class="o">=</span> prueba2
Trace           <span class="o">=</span> 0
TraceFile       <span class="o">=</span> /tmp/sql.log</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>Driver</strong>: Indicamos el nombre del <em>driver</em> previamente configurado en el fichero <strong>/etc/odbcinst.ini</strong>. En este caso, <strong>PostgreSQL</strong>.</li>
  <li><strong>Servername</strong>: Indicamos la dirección IP del servidor PostgreSQL al que deseamos crear el enlace. En este caso, <strong>192.168.1.161</strong>.</li>
  <li><strong>Username</strong>: Indicamos el nombre del usuario para el acceso a la base de datos remota. En este caso, <strong>alvaro2</strong>.</li>
  <li><strong>Password</strong>: Indicamos la contraseña del usuario para el acceso a la base de datos remota. En este caso, <strong>alvaro2</strong>.</li>
  <li><strong>Port</strong>: Indicamos el puerto en el que está escuchando peticiones el servidor PostgreSQL. En este caso, <strong>5432</strong>.</li>
  <li><strong>Database</strong>: Indicamos el nombre de la base de datos remota a la que pretendemos conectarnos. En este caso, <strong>prueba2</strong>.</li>
</ul>

<p>La configuración del <em>driver</em> de ODBC ha finalizado, de manera que es aconsejable hacer una pequeña prueba llegado este punto para verificar su correcto funcionamiento, haciendo para ello uso del binario <code class="language-plaintext highlighter-rouge">isql</code> incluido en el paquete previamente instalado, seguido del <strong>DSN</strong> en cuestión.</p>

<p>Gracias al mismo, podremos realizar una conexión ODBC para así comprobar que es posible acceder a la base de datos PostgreSQL remota haciendo uso de los parámetros que hemos especificado, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# isql PSQLU
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| <span class="nb">help</span> <span class="se">\[</span>tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+</code></pre></figure>

<p>Como se puede apreciar en la salida del comando ejecutado, la conexión ha sido exitosa y actualmente nos encontramos haciendo uso de un cliente en el que podríamos ejecutar órdenes SQL, como por ejemplo listar el contenido de la tabla <strong>Departamentos</strong>, haciendo para ello uso de la instrucción:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">SQL&gt; SELECT <span class="k">*</span> FROM Departamentos<span class="p">;</span>
+--------------+---------------------+----------------+
| identificador| nombre              | localizacion   |
+--------------+---------------------+----------------+
| 10           | Administración     | Sevilla        |
| 20           | Recursos Humanos    | Barcelona      |
| 30           | Seguridad           | Madrid         |
| 40           | Informática        | Valencia       |
+--------------+---------------------+----------------+
SQLRowCount returns 4
4 rows fetched</code></pre></figure>

<p>Efectivamente, la información devuelta concuerda con la almacenada en dicha tabla remota, de manera que podremos salir del cliente ejecutando la instrucción <code class="language-plaintext highlighter-rouge">exit</code> para así continuar con la configuración del enlace.</p>

<p>Como anteriormente hemos mencionado, la configuración del <em>driver</em> ha finalizado, sin embargo, Oracle no está todavía configurado para poder utilizar dicho <em>driver</em>, por lo que el siguiente paso consistirá en generar un fichero en el que se especifiquen determinados parámetros necesarios para ello (<strong><em>Heterogeneous Services</em></strong>).</p>

<p>La ubicación de dicho fichero será <strong>$ORACLE_HOME/hs/admin/</strong> y su nombre, <strong>init[DSN].ora</strong>, de manera que en este caso, al ser <strong>PSQLU</strong> el <strong>DSN</strong>, el nombre del mismo sería <strong>initPSQLU.ora</strong>. Para generar y editar dicho fichero, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/hs/admin/initPSQLU.ora</code></pre></figure>

<p>Dentro del mismo, introduciremos el siguiente contenido:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">HS_FDS_CONNECT_INFO <span class="o">=</span> PSQLU
HS_FDS_TRACE_LEVEL <span class="o">=</span> DEBUG
HS_FDS_SHAREABLE_NAME <span class="o">=</span> /usr/lib64/psqlodbcw.so
HS_LANGUAGE <span class="o">=</span> AMERICAN_AMERICA.WE8ISO8859P1
<span class="nb">set </span><span class="nv">ODBCINI</span><span class="o">=</span>/etc/odbc.ini</code></pre></figure>

<p>Como se puede apreciar, dentro del mismo hemos definido determinados parámetros como el nombre del DSN, el <em>driver</em> que debe utilizar que habrá sido previamente especificado en el fichero <strong>/etc/odbcinst.ini</strong>, el fichero en el que se encuentran definidos los DSN…</p>

<p>Tras ello, todo estará listo para comenzar a configurar el <strong><em>listener</em></strong>, que para quién no conozca lo que es, es el proceso encargado de escuchar las peticiones entrantes en el lado del servidor, cuyo fichero en el que se define su configuración se encuentra almacenado en <strong>$ORACLE_HOME/network/admin/</strong>, con el nombre <strong>listener.ora</strong>, estableciéndose en el mismo configuración entre la que se encuentran las bases de datos para las que va a escuchar peticiones y los puertos en los que va a hacerlo, por lo que procederemos a modificar dicho fichero ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/network/admin/listener.ora</code></pre></figure>

<p>En este caso, tendremos que añadir una nueva entrada que habilitará la escucha hacia el driver ODBC, especificando para ello el DSN, la ruta del directorio principal de Oracle y el programa en cuestión, quedando en mi caso de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">LISTENER <span class="o">=</span>
  <span class="o">(</span>DESCRIPTION_LIST <span class="o">=</span>
    <span class="o">(</span>DESCRIPTION <span class="o">=</span>
      <span class="o">(</span>ADDRESS <span class="o">=</span> <span class="o">(</span>PROTOCOL <span class="o">=</span> TCP<span class="o">)(</span>HOST <span class="o">=</span> oracle1.alvarovf.com<span class="o">)(</span>PORT <span class="o">=</span> 1521<span class="o">))</span>
      <span class="o">(</span>ADDRESS <span class="o">=</span> <span class="o">(</span>PROTOCOL <span class="o">=</span> IPC<span class="o">)(</span>KEY <span class="o">=</span> EXTPROC1521<span class="o">))</span>
    <span class="o">)</span>
  <span class="o">)</span>

<span class="nv">SID_LIST_LISTENER</span><span class="o">=</span>
  <span class="o">(</span><span class="nv">SID_LIST</span><span class="o">=</span>
      <span class="o">(</span><span class="nv">SID_DESC</span><span class="o">=</span>
         <span class="o">(</span><span class="nv">SID_NAME</span><span class="o">=</span>PSQLU<span class="o">)</span>
         <span class="o">(</span><span class="nv">ORACLE_HOME</span><span class="o">=</span>/opt/oracle/product/19c/dbhome_1<span class="o">)</span>
         <span class="o">(</span><span class="nv">PROGRAM</span><span class="o">=</span>dg4odbc<span class="o">)</span>
      <span class="o">)</span>
  <span class="o">)</span></code></pre></figure>

<p>El siguiente paso será modificar el fichero de nombre <strong>tnsnames.ora</strong>, ubicado en <strong>$ORACLE_HOME/network/admin/</strong>, que permite facilitar la tarea de acceso a servidores remotos, indicando en el mismo una entrada por cada uno de los servidores a los que se pretende acceder, que en este caso nos servirá para “mapear” la conexión hacia el driver ODBC. Su configuración no es para nada complicada, así que vamos a llevarla a cabo ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/network/admin/tnsnames.ora</code></pre></figure>

<p>En dicha entrada tendremos que incluir el protocolo, la dirección IP y el puerto (que como es lógico corresponden a la propia máquina local), así como un alias que será el que se utilice a la hora de la conexión, que en este caso coincidirá con el DSN asignado, quedando en mi caso de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">ORCLCDB <span class="o">=</span>
  <span class="o">(</span>DESCRIPTION <span class="o">=</span>
    <span class="o">(</span>ADDRESS <span class="o">=</span> <span class="o">(</span>PROTOCOL <span class="o">=</span> TCP<span class="o">)(</span>HOST <span class="o">=</span> localhost<span class="o">)(</span>PORT <span class="o">=</span> 1521<span class="o">))</span>
    <span class="o">(</span>CONNECT_DATA <span class="o">=</span>
      <span class="o">(</span>SERVER <span class="o">=</span> DEDICATED<span class="o">)</span>
      <span class="o">(</span>SERVICE_NAME <span class="o">=</span> ORCLCDB<span class="o">)</span>
    <span class="o">)</span>
  <span class="o">)</span>

LISTENER_ORCLCDB <span class="o">=</span>
  <span class="o">(</span>ADDRESS <span class="o">=</span> <span class="o">(</span>PROTOCOL <span class="o">=</span> TCP<span class="o">)(</span>HOST <span class="o">=</span> localhost<span class="o">)(</span>PORT <span class="o">=</span> 1521<span class="o">))</span>

ORACLE2 <span class="o">=</span>
  <span class="o">(</span>DESCRIPTION <span class="o">=</span>
    <span class="o">(</span>ADDRESS <span class="o">=</span> <span class="o">(</span>PROTOCOL <span class="o">=</span> TCP<span class="o">)(</span>HOST <span class="o">=</span> 192.168.1.151<span class="o">)(</span>PORT <span class="o">=</span> 1521<span class="o">))</span>
    <span class="o">(</span>CONNECT_DATA <span class="o">=</span>
      <span class="o">(</span>SERVER <span class="o">=</span> DEDICATED<span class="o">)</span>
      <span class="o">(</span>SERVICE_NAME <span class="o">=</span> ORCLCDB<span class="o">)</span>
    <span class="o">)</span>
  <span class="o">)</span>

PSQLU  <span class="o">=</span>
  <span class="o">(</span><span class="nv">DESCRIPTION</span><span class="o">=</span>
    <span class="o">(</span><span class="nv">ADDRESS</span><span class="o">=(</span><span class="nv">PROTOCOL</span><span class="o">=</span>tcp<span class="o">)(</span><span class="nv">HOST</span><span class="o">=</span>localhost<span class="o">)(</span><span class="nv">PORT</span><span class="o">=</span>1521<span class="o">))</span>
    <span class="o">(</span><span class="nv">CONNECT_DATA</span><span class="o">=(</span><span class="nv">SID</span><span class="o">=</span>PSQLU<span class="o">))</span>
    <span class="o">(</span><span class="nv">HS</span><span class="o">=</span>OK<span class="o">)</span>
  <span class="o">)</span></code></pre></figure>

<p>Tras ello, simplemente guardaremos los cambios realizados y todo estará listo para reiniciar el proceso del <em>listener</em>, con la intención de que cargue en memoria la nueva configuración asignada, cambiándonos previamente al usuario <strong>oracle</strong>, pues es el único que tiene actualmente definida en sus variables de entorno la ruta a los binarios de Oracle, ejecutando por tanto el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>root@oracle1 ~]# su - oracle</code></pre></figure>

<p>Cuando nos encontremos haciendo uso del usuario <strong>oracle</strong>, podremos llevar a cabo dicho reinicio, haciendo para ello uso de los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>oracle@oracle1 ~]<span class="nv">$ </span>lsnrctl stop
<span class="o">[</span>oracle@oracle1 ~]<span class="nv">$ </span>lsnrctl start</code></pre></figure>

<p>Una vez que el <em>listener</em> haya sido correctamente reiniciado, es momento de abrir una <em>shell</em> de <em>sqlplus</em> haciendo uso del usuario <strong>c##alvaro1</strong> actualmente existente en el gestor Oracle, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">[</span>oracle@oracle1 ~]<span class="nv">$ </span>sqlplus c##alvaro1/alvaro1

SQL<span class="k">*</span>Plus: Release 19.0.0.0.0 - Production on Thu Feb 4 12:05:36 2021
Version 19.3.0.0.0

Copyright <span class="o">(</span>c<span class="o">)</span> 1982, 2019, Oracle.  All rights reserved.

Connected to an idle instance.</code></pre></figure>

<p>Por último, tendremos que llevar a cabo la creación del enlace, que será muy sencilla, ya que previamente hemos definido los parámetros de la conexión en el fichero <strong>tnsnames.ora</strong>, llevándose a cabo mediante la ejecución del comando:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SQL</span><span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">DATABASE</span> <span class="n">LINK</span> <span class="n">postgreslink</span>
<span class="k">CONNECT</span> <span class="k">TO</span> <span class="nv">"alvaro2"</span> <span class="n">IDENTIFIED</span> <span class="k">BY</span> <span class="nv">"alvaro2"</span>
<span class="k">USING</span> <span class="s1">'PSQLU'</span><span class="p">;</span>

<span class="n">Enlace</span> <span class="n">con</span> <span class="n">la</span> <span class="n">base</span> <span class="n">de</span> <span class="n">datos</span> <span class="n">creado</span><span class="p">.</span></code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>CREATE DATABASE LINK</strong>: Especificamos un nombre identificativo para el enlace.</li>
  <li><strong>CONNECT TO</strong>: Indicamos las credenciales de acceso a la base de datos remota.</li>
  <li><strong>USING</strong>: Indicamos el nombre del alias de la conexión que previamente hemos definido en el fichero <strong>tnsnames.ora</strong>.</li>
</ul>

<p><strong>Nota</strong>: Es importante utilizar comillas dobles (<strong>” “</strong>) para el nombre de usuario y la contraseña, pero comillas simples (<strong>’ ‘</strong>) para el nombre del alias.</p>

<p>Efectivamente, el enlace <strong>postgreslink</strong> ha sido correctamente generado, de manera que vamos a proceder a verificar el funcionamiento de dicho enlace, ejecutando para ello una consulta cuya información mostrada provenga de ambas bases de datos, ubicadas como ya hemos visto, en servidores distintos.</p>

<p>La intención es mostrar los datos de los <strong>Empleados</strong> (tabla que se encuentra en <strong>oracle1</strong>) junto al nombre del departamento al que pertenecen, información que podremos obtener de <strong>Departamentos</strong> (tabla que se encuentra en <strong>servidor2</strong>), utilizando para ello la clave que tienen en común ambas tablas. La consulta a ejecutar sería:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SQL</span><span class="o">&gt;</span> <span class="k">SELECT</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">DNI</span> <span class="k">AS</span> <span class="n">DNI</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Nombre</span> <span class="k">AS</span> <span class="n">Nombre</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Direccion</span> <span class="k">AS</span> <span class="n">Direccion</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Telefono</span> <span class="k">AS</span> <span class="n">Telefono</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">FechaNacimiento</span> <span class="k">AS</span> <span class="n">FechaNacimiento</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Salario</span> <span class="k">AS</span> <span class="n">Salario</span><span class="p">,</span> <span class="n">Departamentos</span><span class="p">.</span><span class="nv">"nombre"</span> <span class="k">AS</span> <span class="n">Departamento</span>
<span class="k">FROM</span> <span class="n">Empleados</span><span class="p">,</span> <span class="nv">"departamentos"</span><span class="o">@</span><span class="n">postgreslink</span> <span class="n">Departamentos</span>
<span class="k">WHERE</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Departamento</span> <span class="o">=</span> <span class="n">Departamentos</span><span class="p">.</span><span class="nv">"identificador"</span><span class="p">;</span>

<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="mi">90389058</span><span class="n">R</span> <span class="n">Joaquin</span> <span class="n">Marrero</span> <span class="n">Covas</span> 	 <span class="k">C</span><span class="o">/</span> <span class="n">Hijuela</span> <span class="n">de</span> <span class="n">Lojo</span><span class="p">,</span> <span class="mi">22</span>    <span class="mi">618385118</span>
<span class="mi">28</span><span class="o">/</span><span class="mi">01</span><span class="o">/</span><span class="mi">97</span>       <span class="mi">1446</span>
<span class="n">Administraci</span><span class="o">??</span><span class="n">n</span>

<span class="mi">67227129</span><span class="n">S</span> <span class="n">Ian</span> <span class="n">Esquivel</span> <span class="n">Laboy</span>		 <span class="k">C</span><span class="o">/</span> <span class="n">Inglaterra</span><span class="p">,</span> <span class="mi">64</span>	   <span class="mi">728005136</span>
<span class="mi">21</span><span class="o">/</span><span class="mi">07</span><span class="o">/</span><span class="mi">92</span>       <span class="mi">3519</span>
<span class="n">Administraci</span><span class="o">??</span><span class="n">n</span>

<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>

<span class="mi">85145590</span><span class="k">G</span> <span class="n">Anabel</span> <span class="n">Lerma</span> <span class="n">Dominguez</span>	 <span class="n">Crta</span><span class="p">.</span> <span class="n">Cadiz</span><span class="p">,</span> <span class="mi">1</span> 	   <span class="mi">675014823</span>
<span class="mi">07</span><span class="o">/</span><span class="mi">01</span><span class="o">/</span><span class="mi">81</span>       <span class="mi">5919</span>
<span class="n">Administraci</span><span class="o">??</span><span class="n">n</span>

<span class="mi">12777631</span><span class="k">G</span> <span class="n">Merlino</span> <span class="n">Rosado</span> <span class="n">Cordero</span>	 <span class="k">C</span><span class="o">/</span> <span class="n">Henan</span> <span class="n">Cortes</span><span class="p">,</span> <span class="mi">58</span>	   <span class="mi">609841755</span>
<span class="mi">27</span><span class="o">/</span><span class="mi">11</span><span class="o">/</span><span class="mi">93</span>       <span class="mi">7961</span>

<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">Recursos</span> <span class="n">Humanos</span>

<span class="mi">52315160</span><span class="k">G</span> <span class="n">Heinz</span> <span class="n">Collado</span> <span class="n">Caraballo</span>	 <span class="n">Escuadro</span><span class="p">,</span> <span class="mi">60</span>		   <span class="mi">600173822</span>
<span class="mi">13</span><span class="o">/</span><span class="mi">02</span><span class="o">/</span><span class="mi">84</span>       <span class="mi">1672</span>
<span class="n">Recursos</span> <span class="n">Humanos</span>

<span class="mi">18232747</span><span class="n">A</span> <span class="n">Aristarco</span> <span class="n">Caban</span> <span class="n">Meraz</span> 	 <span class="n">Puerta</span> <span class="n">Nueva</span><span class="p">,</span> <span class="mi">67</span>	   <span class="mi">691204722</span>

<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="mi">07</span><span class="o">/</span><span class="mi">05</span><span class="o">/</span><span class="mi">94</span>       <span class="mi">4789</span>
<span class="n">Seguridad</span>

<span class="mi">94106513</span><span class="n">N</span> <span class="n">Marian</span> <span class="n">Fonseca</span> <span class="n">Betancourt</span>	 <span class="k">C</span><span class="o">/</span> <span class="n">Manuel</span> <span class="n">Iradier</span><span class="p">,</span> <span class="mi">37</span>	   <span class="mi">638415823</span>
<span class="mi">17</span><span class="o">/</span><span class="mi">07</span><span class="o">/</span><span class="mi">79</span>       <span class="mi">2561</span>
<span class="n">Seguridad</span>


<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="mi">56228957</span><span class="n">Y</span> <span class="n">Dinorah</span> <span class="n">Viera</span> <span class="n">Tello</span>		 <span class="n">Ctra</span><span class="p">.</span> <span class="n">Villena</span><span class="p">,</span> <span class="mi">22</span>	   <span class="mi">642852778</span>
<span class="mi">28</span><span class="o">/</span><span class="mi">05</span><span class="o">/</span><span class="mi">87</span>       <span class="mi">2567</span>
<span class="n">Seguridad</span>

<span class="mi">68219319</span><span class="n">P</span> <span class="n">Tabare</span> <span class="n">Chapa</span> <span class="n">Alcantar</span> 	 <span class="k">C</span><span class="o">/</span> <span class="n">Arana</span><span class="p">,</span> <span class="mi">12</span>		   <span class="mi">682227206</span>
<span class="mi">09</span><span class="o">/</span><span class="mi">03</span><span class="o">/</span><span class="mi">92</span>       <span class="mi">8568</span>
<span class="n">Inform</span><span class="o">?!</span><span class="n">tica</span>

<span class="n">DNI</span>	  <span class="n">NOMBRE</span>			 <span class="n">DIRECCION</span>		   <span class="n">TELEFONO</span>
<span class="c1">--------- ------------------------------ ------------------------- ---------
</span>
<span class="n">FECHANAC</span>    <span class="n">SALARIO</span>
<span class="c1">-------- ----------
</span>
<span class="n">DEPARTAMENTO</span>
<span class="c1">--------------------------------------------------------------------------------
</span>

<span class="mi">61242562</span><span class="n">W</span> <span class="n">Manases</span> <span class="n">Castillo</span> <span class="n">Camacho</span>	 <span class="n">Ctra</span><span class="p">.</span> <span class="n">Hornos</span><span class="p">,</span> <span class="mi">91</span>	   <span class="mi">607853354</span>
<span class="mi">29</span><span class="o">/</span><span class="mi">10</span><span class="o">/</span><span class="mi">91</span>       <span class="mi">4270</span>
<span class="n">Inform</span><span class="o">?!</span><span class="n">tica</span>


<span class="mi">10</span> <span class="n">filas</span> <span class="n">seleccionadas</span><span class="p">.</span></code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>SELECT</strong>: Indicamos las columnas que queremos mostrar de la información obtenida, de la forma <strong>[tabla].[columna]</strong>.</li>
  <li><strong>FROM</strong>: Hacemos un JOIN de la tabla Empleados que se encuentra en la base de datos local junto a la consulta remota, haciendo uso del enlace que acabamos de generar, para así poder mostrar información de ambas tablas en la misma consulta.</li>
  <li><strong>WHERE</strong>: Establecemos la condición del JOIN, que deberá ser aquella columna mediante la cual vamos a unir los registros devueltos. Como es lógico, será el código del departamento, pues es la columna que se repite en ambas tablas.</li>
</ul>

<p><strong>Nota</strong>: Es importante utilizar comillas dobles (<strong>” “</strong>) para el nombre de las tablas y las columnas siempre que se haga referencia al gestor PostgreSQL, indicando el nombre de las mismas en minúsculas.</p>

<p>Al parecer, la consulta se ha realizado sin ningún problema y ha devuelto la información que debería, pues tal y como he mencionado con anterioridad, ambas máquinas servidoras cuentan con un direccionamiento dentro de la red local, siendo ambas totalmente alcanzables entre sí, además de estar correctamente configuradas para aceptar dichas conexiones.</p>

<p>Otra utilidad que estos enlaces nos aportan es la capacidad de copiar las tablas de un gestor a otro, utilizando el resultado de una consulta simple para crear una tabla a partir de la misma. Por ejemplo, podríamos copiar la tabla <strong>Departamentos</strong> haciendo uso de la siguiente instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SQL</span><span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Departamentos</span>
<span class="k">AS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="o">*</span>
    <span class="k">FROM</span> <span class="nv">"departamentos"</span><span class="o">@</span><span class="n">postgreslink</span><span class="p">);</span>

<span class="n">Tabla</span> <span class="n">creada</span><span class="p">.</span></code></pre></figure>

<p>En dicha instrucción, hemos realizado una consulta a la tabla <strong>Departamentos</strong> ubicada en la base de datos del servidor <strong>servidor2</strong>, utilizando la respuesta obtenida para crear una nueva tabla con el mismo nombre, que se almacenará ahora de forma local en el servidor <strong>oracle1</strong>, y que podremos empezar a utilizar sin necesidad de recurrir al enlace con el segundo servidor. Si consultamos la nueva tabla generada, obtendremos el siguiente resultado:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SQL</span><span class="o">&gt;</span> <span class="k">SELECT</span> <span class="o">*</span>
  <span class="mi">2</span>  <span class="k">FROM</span> <span class="n">Departamentos</span><span class="p">;</span>

<span class="n">identificador</span>
<span class="c1">-------------
</span>
<span class="n">nombre</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">localizacion</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
	   <span class="mi">10</span>
<span class="n">Administraci</span><span class="o">??</span><span class="n">n</span>
<span class="n">Sevilla</span>

	   <span class="mi">20</span>
<span class="n">Recursos</span> <span class="n">Humanos</span>
<span class="n">Barcelona</span>

<span class="n">identificador</span>
<span class="c1">-------------
</span>
<span class="n">nombre</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">localizacion</span>
<span class="c1">--------------------------------------------------------------------------------
</span>

	   <span class="mi">30</span>
<span class="n">Seguridad</span>
<span class="n">Madrid</span>

	   <span class="mi">40</span>
<span class="n">Inform</span><span class="o">?!</span><span class="n">tica</span>

<span class="n">identificador</span>
<span class="c1">-------------
</span>
<span class="n">nombre</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">localizacion</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">Valencia</span></code></pre></figure>

<p>Como se puede apreciar, el contenido es exactamente el mismo que el existente en la tabla ubicada en el gestor remoto, por lo que podemos concluir que su clonación ha sido efectiva.</p>

<p>El enlace ha funcionado del extremo <strong>oracle1</strong> al extremo <strong>servidor2</strong>, pero como ya sabemos, dichos enlaces son unidireccionales, de manera que si quisiésemos realizar la conexión a la inversa, de <strong>PostgreSQL</strong> a <strong>Oracle</strong>, tendríamos que llevar a cabo un proceso un tanto distinto, así que vamos a proceder a ello.</p>

<h2 id="postgresql-a-oracle-19c">PostgreSQL a Oracle 19c</h2>

<p>Una vez más, partimos del punto en el que tanto el servidor <strong>PostgreSQL</strong> como el <strong>Oracle 19c</strong> están totalmente operativos y listos para escuchar peticiones remotas. Como en anteriores artículos hemos visto, los servidores PostgreSQL cuentan con soporte nativo para realizar enlaces con otros servidores PostgreSQL, sin embargo, no ocurre lo mismo cuando deseamos conectarnos a otro gestor totalmente diferente, como puede ser Oracle.</p>

<p>En estos casos, podemos acudir a <strong>oracle_fdw</strong> (<em>Foreign Data Wrapper for Oracle</em>), un concepto que introdujo PostgreSQL hace varias versiones cuyo objetivo es hacer posible el acceso de una forma muy sencilla y eficiente a las bases de datos Oracle, tal y como mostraremos a continuación. Existen otros <em>Data Wrappers</em>, que posibilitan una integración con muchos otros gestores como <strong>MySQL</strong> e incluso gestores no relacionales como <strong>MongoDB</strong>.</p>

<p><img src="https://i.ibb.co/YhtZR1G/0-0nv5okg-JCc-PPXUEk.png" alt="diagrama2" title="Diagrama enlace" /></p>

<p>El paquete <strong>oracle_fdw</strong> no se encuentra actualmente disponible en los repositorios oficiales de CentOS 8, sino que tendremos que compilarlo a mano. Por ese mismo motivo, necesitaremos instalar una serie de paquetes que nos proporcionarán las herramientas necesarias para llevar a cabo dicha compilación, así como los paquetes oficiales de <strong><em>Oracle Instant Client</em></strong>.</p>

<p>Comenzaremos por la instalación de aquellos paquetes esenciales para la compilación, no sin antes actualizar la lista de la paquetería disponible, por lo que haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@servidor2:/home/postgres# apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>libaio1 postgresql-server-dev-all build-essential git</code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>libaio1</strong>: Biblioteca activa en el espacio de usuario importante para el rendimiento de las bases de datos y otras aplicaciones avanzadas.</li>
  <li><strong>postgresql-server-dev-all</strong>: Proporciona las herramientas necesarias para la compilación de extensiones para PostgreSQL.</li>
  <li><strong>build-essential</strong>: Como su propio nombre indica, contiene las utilidades esenciales para compilar un paquete en Debian (compiladores y bibliotecas).</li>
  <li><strong>git</strong>: Nos permitirá clonar el repositorio GitHub en el que está contenido el código fuente de la aplicación.</li>
</ul>

<p>Una vez instalados los paquetes esenciales, tendremos que descargar también los paquetes oficiales de <strong><em>Oracle Instant Client</em></strong>, que nos permitirán hacer uso del cliente Oracle para llevar a cabo las conexiones a las bases de datos remotas. Antes de ello, vamos a cambiarnos al usuario <strong>postgres</strong>, pues será aquel que utilizará principalmente dichos binarios, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">root@servidor2:/home/postgres# su postgres</code></pre></figure>

<p>Los paquetes que debemos descargar del <a href="https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html">sitio</a> oficial de Oracle son los siguientes:</p>

<ul>
  <li><a href="https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-basic-linux.x64-21.1.0.0.0.zip">instantclient-basic-linux.x64-21.1.0.0.0.zip</a></li>
  <li><a href="https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sdk-linux.x64-21.1.0.0.0.zip">instantclient-sdk-linux.x64-21.1.0.0.0.zip</a></li>
  <li><a href="https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sqlplus-linux.x64-21.1.0.0.0.zip">instantclient-sqlplus-linux.x64-21.1.0.0.0.zip</a></li>
</ul>

<p>Para llevar a cabo la descarga de dichos paquetes, haremos uso de los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-basic-linux.x64-21.1.0.0.0.zip
postgres@servidor2:/home/postgres<span class="nv">$ </span>wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sdk-linux.x64-21.1.0.0.0.zip
postgres@servidor2:/home/postgres<span class="nv">$ </span>wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sqlplus-linux.x64-21.1.0.0.0.zip</code></pre></figure>

<p>Tras ello, vamos a listar el contenido del directorio actual para verificar que las descargas se han completado satisfactoriamente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 79288
<span class="nt">-rw-r--r--</span> 1 postgres postgres 79250994 Dec  1 12:07 instantclient-basic-linux.x64-21.1.0.0.0.zip
<span class="nt">-rw-r--r--</span> 1 postgres postgres   998327 Dec  1 12:07 instantclient-sdk-linux.x64-21.1.0.0.0.zip
<span class="nt">-rw-r--r--</span> 1 postgres postgres   936169 Dec  1 12:07 instantclient-sqlplus-linux.x64-21.1.0.0.0.zip</code></pre></figure>

<p>Efectivamente, se han descargado los siguientes ficheros:</p>

<ul>
  <li><strong>instantclient-basic-linux.x64-21.1.0.0.0.zip</strong> de peso total <strong>75.58M</strong> (79250994 bytes).</li>
  <li><strong>instantclient-sdk-linux.x64-21.1.0.0.0.zip</strong> de peso total <strong>974.93K</strong> (998327 bytes).</li>
  <li><strong>instantclient-sqlplus-linux.x64-21.1.0.0.0.zip</strong> de peso total <strong>914.23K</strong> (936169 bytes).</li>
</ul>

<p>Si nos fijamos, los paquetes están comprimidos, por lo que no podremos utilizar los binarios incluidos hasta que no hagamos una extracción de los mismos. Además, para liberar espacio, borraremos tras ello los ficheros comprimidos ya que no nos harán falta. Para descomprimir un paquete <strong>.zip</strong> haremos uso de <code class="language-plaintext highlighter-rouge">unzip</code>, ejecutando los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>unzip instantclient-basic-linux.x64-21.1.0.0.0.zip
postgres@servidor2:/home/postgres<span class="nv">$ </span>unzip instantclient-sqlplus-linux.x64-21.1.0.0.0.zip
postgres@servidor2:/home/postgres<span class="nv">$ </span>unzip instantclient-sdk-linux.x64-21.1.0.0.0.zip
postgres@servidor2:/home/postgres<span class="nv">$ </span><span class="nb">rm</span> <span class="k">*</span>.zip</code></pre></figure>

<p>Para verificar que los ficheros se han descomprimido y eliminado correctamente, haremos uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 4
drwxr-xr-x 4 postgres postgres 4096 Feb  4 11:28 instantclient_21_1</code></pre></figure>

<p>Efectivamente, todo el contenido se ha descomprimido tal y como queríamos en un directorio de nombre <strong>instantclient_21_1</strong>, que contendrá todos los binarios que nos ofrece un cliente Oracle, entre los que se encuentra nuestro preciado <code class="language-plaintext highlighter-rouge">sqlplus</code>.</p>

<p>Para verificarlo, podemos listar los ejecutables de dicho directorio, estableciendo un filtro por nombre para no mostrar las librerías compartidas, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>find instantclient_21_1/ <span class="nt">-executable</span> <span class="nt">-type</span> f | egrep <span class="nt">-v</span> <span class="s1">'.so.*$'</span>
instantclient_21_1/sqlplus
instantclient_21_1/sdk/ott
instantclient_21_1/uidrvci
instantclient_21_1/adrci
instantclient_21_1/genezi</code></pre></figure>

<p>Sin embargo, si ahora mismo quisiésemos hacer uso de cualquiera de esos binarios, tendríamos que indicar la ruta completa para que el sistema sea capaz de encontrarlo. Esto tiene una razón, y es que en Linux existe una variable de entorno de nombre <strong>PATH</strong> ($PATH) que indica las rutas en las que buscar los binarios (y en qué orden), en la que no se encuentra actualmente especificada la ruta <strong>/home/postgres/instantclient_21_1</strong>.</p>

<p>Para solucionar esto entre otras cosas, vamos a definir determinadas variables de entorno necesarias para el correcto funcionamiento de dichos binarios, entre las que se encuentra el <strong>$ORACLE_HOME</strong>, que será aquella ruta en la que se encuentran ubicados los binarios en cuestión. Además, anexaremos el valor de dicha variable a <strong>$LD_LIBRARY_PATH</strong> y <strong>$PATH</strong>, haciendo para ello uso de los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:~<span class="nv">$ </span><span class="nb">export </span><span class="nv">ORACLE_HOME</span><span class="o">=</span>/home/postgres/instantclient_21_1
postgres@servidor2:~<span class="nv">$ </span><span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="nv">$LD_LIBRARY_PATH</span>:<span class="nv">$ORACLE_HOME</span>
postgres@servidor2:~<span class="nv">$ </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$ORACLE_HOME</span></code></pre></figure>

<p>Al haber añadido la nueva ruta a la variable <strong>$PATH</strong>, ya será posible encontrar dicho binario sin indicar su ruta completa, así que vamos a comprobarlo haciendo uso de <code class="language-plaintext highlighter-rouge">which</code>, que nos devolverá la ruta en la que se ubica un binario en cuestión, por ejemplo <code class="language-plaintext highlighter-rouge">sqlplus</code>, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>which sqlplus
/home/postgres/instantclient_21_1/sqlplus</code></pre></figure>

<p>Como era de esperar, gracias a haber añadido la nueva ruta a dicha variable de entorno, el binario es ahora totalmente accesible.</p>

<p>Para verificar el correcto funcionamiento del mismo, vamos a llevar a cabo una conexión remota real a la base de datos, como si de una situación normal y corriente se tratase, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>sqlplus c##alvaro1/alvaro1@192.168.1.150/ORCLCDB

SQL<span class="k">*</span>Plus: Release 21.0.0.0.0 - Production on Thu Feb 4 11:34:53 2021
Version 21.1.0.0.0

Copyright <span class="o">(</span>c<span class="o">)</span> 1982, 2020, Oracle.  All rights reserved.

Hora de Ultima Conexion Correcta: Jue Feb 04 2021 11:12:43 <span class="nt">-05</span>:00

Conectado a:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0</code></pre></figure>

<p>Tal y como se puede apreciar en la salida del comando, nos hemos logrado conectar correctamente a la base de datos ubicada en la primera máquina servidora, por lo que vamos a proceder a listar todas las tablas que el usuario <strong>c##alvaro1</strong> tiene privilegios para ver, es decir, aquellas que hemos creado manualmente. Para ello, ejecutaremos la instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SQL</span><span class="o">&gt;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">cat</span><span class="p">;</span>

<span class="k">TABLE_NAME</span>
<span class="c1">--------------------------------------------------------------------------------
</span>
<span class="n">TABLE_TYPE</span>
<span class="c1">-----------
</span>
<span class="n">EMPLEADOS</span>
<span class="k">TABLE</span></code></pre></figure>

<p>Efectivamente, la tabla que anteriormente hemos creado de forma manual es visible y accesible de forma remota desde una máquina PostgreSQL que actualmente actúa como cliente, de manera que podremos salir de dicho cliente ejecutando la instrucción <code class="language-plaintext highlighter-rouge">exit</code> para así continuar con la configuración del enlace.</p>

<p>Tras comprobar que los binarios del cliente Oracle funcionan correctamente, es hora de llevar a cabo la compilación de <strong>oracle_fdw</strong>, de manera que tendremos que clonar el <a href="https://github.com/laurenz/oracle_fdw">repositorio</a> GitHub en el que está contenido el código fuente necesario para ello, haciendo por tanto uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span>git clone https://github.com/laurenz/oracle_fdw.git
Cloning into <span class="s1">'oracle_fdw'</span>...
remote: Enumerating objects: 77, <span class="k">done</span><span class="nb">.</span>
remote: Counting objects: 100% <span class="o">(</span>77/77<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Compressing objects: 100% <span class="o">(</span>54/54<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Total 2223 <span class="o">(</span>delta 39<span class="o">)</span>, reused 56 <span class="o">(</span>delta 23<span class="o">)</span>, pack-reused 2146
Receiving objects: 100% <span class="se">\(</span>2223/2223<span class="o">)</span>, 1.35 MiB | 2.58 MiB/s, <span class="k">done</span><span class="nb">.</span>
Resolving deltas: 100% <span class="o">(</span>1513/1513<span class="o">)</span>, <span class="k">done</span>.</code></pre></figure>

<p>Una vez finalizada la clonación, vamos a listar una vez más el contenido del directorio actual para así verificar que dicha clonación ha sido efectiva:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 8
drwxr-xr-x 4 postgres postgres 4096 Feb  4 11:28 instantclient_21_1
drwxr-xr-x 6 postgres postgres 4096 Feb  4 11:39 oracle_fdw</code></pre></figure>

<p>Efectivamente, el repositorio de nombre <strong>oracle_fdw</strong> ha sido correctamente clonado en nuestra máquina local y podremos empezar a hacer uso del mismo, de manera que nos moveremos dentro dicho directorio para visualizar su contenido, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres<span class="nv">$ </span><span class="nb">cd </span>oracle_fdw/</code></pre></figure>

<p>Tras ello, listaremos el contenido existente una vez más para verificar que el repositorio se ha clonado correctamente:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres/oracle_fdw<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span>
total 460
<span class="nt">-rw-r--r--</span> 1 postgres postgres  20254 Feb  4 11:39 CHANGELOG
drwxr-xr-x 2 postgres postgres   4096 Feb  4 11:39 expected
<span class="nt">-rw-r--r--</span> 1 postgres postgres   1086 Feb  4 11:39 LICENSE
<span class="nt">-rw-r--r--</span> 1 postgres postgres   2932 Feb  4 11:39 Makefile
drwxr-xr-x 2 postgres postgres   4096 Feb  4 11:39 msvc
<span class="nt">-rw-r--r--</span> 1 postgres postgres    231 Feb  4 11:39 oracle_fdw--1.0--1.1.sql
<span class="nt">-rw-r--r--</span> 1 postgres postgres    240 Feb  4 11:39 oracle_fdw--1.1--1.2.sql
<span class="nt">-rw-r--r--</span> 1 postgres postgres   1244 Feb  4 11:39 oracle_fdw--1.2.sql
<span class="nt">-rw-r--r--</span> 1 postgres postgres 207606 Feb  4 11:39 oracle_fdw.c
<span class="nt">-rw-r--r--</span> 1 postgres postgres    133 Feb  4 11:39 oracle_fdw.control
<span class="nt">-rw-r--r--</span> 1 postgres postgres   8667 Feb  4 11:39 oracle_fdw.h
<span class="nt">-rw-r--r--</span> 1 postgres postgres  44511 Feb  4 11:39 oracle_gis.c
<span class="nt">-rw-r--r--</span> 1 postgres postgres  98942 Feb  4 11:39 oracle_utils.c
lrwxrwxrwx 1 postgres postgres     17 Feb  4 11:39 README.md -&gt; README.oracle_fdw
<span class="nt">-rw-r--r--</span> 1 postgres postgres  39227 Feb  4 11:39 README.oracle_fdw
drwxr-xr-x 2 postgres postgres   4096 Feb  4 11:39 sql
<span class="nt">-rw-r--r--</span> 1 postgres postgres    313 Feb  4 11:39 TODO</code></pre></figure>

<p>Efectivamente, todos los componentes necesarios para la compilación se encuentran ubicados dentro del directorio en cuestión.</p>

<p>Explicar con detenimiento cómo funciona una compilación sería bastante extenso, por lo que <a href="https://www.alvarovf.com/sistemas/2020/10/31/compilacion-usando-makefile.html">aquí</a> se puede encontrar otro artículo de mi blog en el que se explica y muestra un ejemplo práctico, aunque no es un requisito conocer cómo funciona para hacerlo.</p>

<p>En resumen, cuando queremos compilar programas “grandes”, en los que hay gran cantidad de ficheros fuente (<strong>.c</strong>), <em>headers</em> (<strong>.h</strong>) y librerías, sería totalmente inviable compilarlos todos a mano para posteriormente <em>linkar</em> todos los <strong>.o</strong> en un único ejecutable. Es por ello que recurrimos al comando <strong>make</strong>, que nos permite hacerlo de una manera mucho más eficiente e inteligente gracias a un fichero de nombre <strong>Makefile</strong>, en el que se define mediante instrucciones, el orden en el que se compilarán los ficheros <strong>.c</strong> y <em>linkarán</em> los ficheros <strong>.o</strong>.</p>

<p>Por tanto, para llevar a cabo dicha compilación e instalar el resultado en los correspondientes directorios de la máquina, ejecutaremos los comandos:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres/oracle_fdw<span class="nv">$ </span>make
gcc <span class="nt">-Wall</span> <span class="nt">-Wmissing-prototypes</span> <span class="nt">-Wpointer-arith</span> <span class="nt">-Wdeclaration-after-statement</span> <span class="nt">-Wendif-labels</span> <span class="nt">-Wmissing-format-attribute</span> <span class="nt">-Wformat-security</span> <span class="nt">-fno-strict-aliasing</span> <span class="nt">-fwrapv</span> <span class="nt">-fexcess-precision</span><span class="o">=</span>standard <span class="nt">-Wno-format-truncation</span> <span class="nt">-Wno-stringop-truncation</span> <span class="nt">-g</span> <span class="nt">-g</span> <span class="nt">-O2</span> <span class="nt">-fstack-protector-strong</span> <span class="nt">-Wformat</span> <span class="nt">-Werror</span><span class="o">=</span>format-security <span class="nt">-fno-omit-frame-pointer</span> <span class="nt">-fPIC</span> <span class="nt">-I</span>/home/postgres/instantclient_21_1/sdk/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/oci/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/rdbms/public <span class="nt">-I</span>/home/postgres/instantclient_21_1 <span class="nt">-I</span>/usr/include/oracle/19.9/client <span class="nt">-I</span>/usr/include/oracle/19.9/client64 <span class="nt">-I</span>/usr/include/oracle/19.8/client <span class="nt">-I</span>/usr/include/oracle/19.8/client64 <span class="nt">-I</span>/usr/include/oracle/19.6/client <span class="nt">-I</span>/usr/include/oracle/19.6/client64 <span class="nt">-I</span>/usr/include/oracle/19.3/client <span class="nt">-I</span>/usr/include/oracle/19.3/client64 <span class="nt">-I</span>/usr/include/oracle/18.5/client <span class="nt">-I</span>/usr/include/oracle/18.5/client64 <span class="nt">-I</span>/usr/include/oracle/18.3/client <span class="nt">-I</span>/usr/include/oracle/18.3/client64 <span class="nt">-I</span>/usr/include/oracle/12.2/client <span class="nt">-I</span>/usr/include/oracle/12.2/client64 <span class="nt">-I</span>/usr/include/oracle/12.1/client <span class="nt">-I</span>/usr/include/oracle/12.1/client64 <span class="nt">-I</span>/usr/include/oracle/11.2/client <span class="nt">-I</span>/usr/include/oracle/11.2/client64 <span class="nt">-I</span>/usr/include/oracle/11.1/client <span class="nt">-I</span>/usr/include/oracle/11.1/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client64 <span class="nt">-I</span><span class="nb">.</span> <span class="nt">-I</span>./ <span class="nt">-I</span>/usr/include/postgresql/11/server <span class="nt">-I</span>/usr/include/postgresql/internal  <span class="nt">-Wdate-time</span> <span class="nt">-D_FORTIFY_SOURCE</span><span class="o">=</span>2 <span class="nt">-D_GNU_SOURCE</span> <span class="nt">-I</span>/usr/include/libxml2  <span class="nt">-I</span>/usr/include/mit-krb5  <span class="nt">-c</span> <span class="nt">-o</span> oracle_fdw.o oracle_fdw.c
gcc <span class="nt">-Wall</span> <span class="nt">-Wmissing-prototypes</span> <span class="nt">-Wpointer-arith</span> <span class="nt">-Wdeclaration-after-statement</span> <span class="nt">-Wendif-labels</span> <span class="nt">-Wmissing-format-attribute</span> <span class="nt">-Wformat-security</span> <span class="nt">-fno-strict-aliasing</span> <span class="nt">-fwrapv</span> <span class="nt">-fexcess-precision</span><span class="o">=</span>standard <span class="nt">-Wno-format-truncation</span> <span class="nt">-Wno-stringop-truncation</span> <span class="nt">-g</span> <span class="nt">-g</span> <span class="nt">-O2</span> <span class="nt">-fstack-protector-strong</span> <span class="nt">-Wformat</span> <span class="nt">-Werror</span><span class="o">=</span>format-security <span class="nt">-fno-omit-frame-pointer</span> <span class="nt">-fPIC</span> <span class="nt">-I</span>/home/postgres/instantclient_21_1/sdk/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/oci/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/rdbms/public <span class="nt">-I</span>/home/postgres/instantclient_21_1 <span class="nt">-I</span>/usr/include/oracle/19.9/client <span class="nt">-I</span>/usr/include/oracle/19.9/client64 <span class="nt">-I</span>/usr/include/oracle/19.8/client <span class="nt">-I</span>/usr/include/oracle/19.8/client64 <span class="nt">-I</span>/usr/include/oracle/19.6/client <span class="nt">-I</span>/usr/include/oracle/19.6/client64 <span class="nt">-I</span>/usr/include/oracle/19.3/client <span class="nt">-I</span>/usr/include/oracle/19.3/client64 <span class="nt">-I</span>/usr/include/oracle/18.5/client <span class="nt">-I</span>/usr/include/oracle/18.5/client64 <span class="nt">-I</span>/usr/include/oracle/18.3/client <span class="nt">-I</span>/usr/include/oracle/18.3/client64 <span class="nt">-I</span>/usr/include/oracle/12.2/client <span class="nt">-I</span>/usr/include/oracle/12.2/client64 <span class="nt">-I</span>/usr/include/oracle/12.1/client <span class="nt">-I</span>/usr/include/oracle/12.1/client64 <span class="nt">-I</span>/usr/include/oracle/11.2/client <span class="nt">-I</span>/usr/include/oracle/11.2/client64 <span class="nt">-I</span>/usr/include/oracle/11.1/client <span class="nt">-I</span>/usr/include/oracle/11.1/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client64 <span class="nt">-I</span><span class="nb">.</span> <span class="nt">-I</span>./ <span class="nt">-I</span>/usr/include/postgresql/11/server <span class="nt">-I</span>/usr/include/postgresql/internal  <span class="nt">-Wdate-time</span> <span class="nt">-D_FORTIFY_SOURCE</span><span class="o">=</span>2 <span class="nt">-D_GNU_SOURCE</span> <span class="nt">-I</span>/usr/include/libxml2  <span class="nt">-I</span>/usr/include/mit-krb5  <span class="nt">-c</span> <span class="nt">-o</span> oracle_utils.o oracle_utils.c
gcc <span class="nt">-Wall</span> <span class="nt">-Wmissing-prototypes</span> <span class="nt">-Wpointer-arith</span> <span class="nt">-Wdeclaration-after-statement</span> <span class="nt">-Wendif-labels</span> <span class="nt">-Wmissing-format-attribute</span> <span class="nt">-Wformat-security</span> <span class="nt">-fno-strict-aliasing</span> <span class="nt">-fwrapv</span> <span class="nt">-fexcess-precision</span><span class="o">=</span>standard <span class="nt">-Wno-format-truncation</span> <span class="nt">-Wno-stringop-truncation</span> <span class="nt">-g</span> <span class="nt">-g</span> <span class="nt">-O2</span> <span class="nt">-fstack-protector-strong</span> <span class="nt">-Wformat</span> <span class="nt">-Werror</span><span class="o">=</span>format-security <span class="nt">-fno-omit-frame-pointer</span> <span class="nt">-fPIC</span> <span class="nt">-I</span>/home/postgres/instantclient_21_1/sdk/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/oci/include <span class="nt">-I</span>/home/postgres/instantclient_21_1/rdbms/public <span class="nt">-I</span>/home/postgres/instantclient_21_1 <span class="nt">-I</span>/usr/include/oracle/19.9/client <span class="nt">-I</span>/usr/include/oracle/19.9/client64 <span class="nt">-I</span>/usr/include/oracle/19.8/client <span class="nt">-I</span>/usr/include/oracle/19.8/client64 <span class="nt">-I</span>/usr/include/oracle/19.6/client <span class="nt">-I</span>/usr/include/oracle/19.6/client64 <span class="nt">-I</span>/usr/include/oracle/19.3/client <span class="nt">-I</span>/usr/include/oracle/19.3/client64 <span class="nt">-I</span>/usr/include/oracle/18.5/client <span class="nt">-I</span>/usr/include/oracle/18.5/client64 <span class="nt">-I</span>/usr/include/oracle/18.3/client <span class="nt">-I</span>/usr/include/oracle/18.3/client64 <span class="nt">-I</span>/usr/include/oracle/12.2/client <span class="nt">-I</span>/usr/include/oracle/12.2/client64 <span class="nt">-I</span>/usr/include/oracle/12.1/client <span class="nt">-I</span>/usr/include/oracle/12.1/client64 <span class="nt">-I</span>/usr/include/oracle/11.2/client <span class="nt">-I</span>/usr/include/oracle/11.2/client64 <span class="nt">-I</span>/usr/include/oracle/11.1/client <span class="nt">-I</span>/usr/include/oracle/11.1/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.5/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.4/client64 <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client <span class="nt">-I</span>/usr/include/oracle/10.2.0.3/client64 <span class="nt">-I</span><span class="nb">.</span> <span class="nt">-I</span>./ <span class="nt">-I</span>/usr/include/postgresql/11/server <span class="nt">-I</span>/usr/include/postgresql/internal  <span class="nt">-Wdate-time</span> <span class="nt">-D_FORTIFY_SOURCE</span><span class="o">=</span>2 <span class="nt">-D_GNU_SOURCE</span> <span class="nt">-I</span>/usr/include/libxml2  <span class="nt">-I</span>/usr/include/mit-krb5  <span class="nt">-c</span> <span class="nt">-o</span> oracle_gis.o oracle_gis.c
gcc <span class="nt">-Wall</span> <span class="nt">-Wmissing-prototypes</span> <span class="nt">-Wpointer-arith</span> <span class="nt">-Wdeclaration-after-statement</span> <span class="nt">-Wendif-labels</span> <span class="nt">-Wmissing-format-attribute</span> <span class="nt">-Wformat-security</span> <span class="nt">-fno-strict-aliasing</span> <span class="nt">-fwrapv</span> <span class="nt">-fexcess-precision</span><span class="o">=</span>standard <span class="nt">-Wno-format-truncation</span> <span class="nt">-Wno-stringop-truncation</span> <span class="nt">-g</span> <span class="nt">-g</span> <span class="nt">-O2</span> <span class="nt">-fstack-protector-strong</span> <span class="nt">-Wformat</span> <span class="nt">-Werror</span><span class="o">=</span>format-security <span class="nt">-fno-omit-frame-pointer</span> <span class="nt">-fPIC</span> <span class="nt">-shared</span> <span class="nt">-o</span> oracle_fdw.so oracle_fdw.o oracle_utils.o oracle_gis.o <span class="nt">-L</span>/usr/lib/x86_64-linux-gnu  <span class="nt">-Wl</span>,-z,relro <span class="nt">-Wl</span>,-z,now <span class="nt">-L</span>/usr/lib/llvm-7/lib  <span class="nt">-L</span>/usr/lib/x86_64-linux-gnu/mit-krb5 <span class="nt">-Wl</span>,--as-needed  <span class="nt">-L</span>/home/postgres/instantclient_21_1 <span class="nt">-L</span>/home/postgres/instantclient_21_1/bin <span class="nt">-L</span>/home/postgres/instantclient_21_1/lib <span class="nt">-L</span>/home/postgres/instantclient_21_1/lib/amd64 <span class="nt">-lclntsh</span> <span class="nt">-L</span>/usr/lib/oracle/19.9/client/lib <span class="nt">-L</span>/usr/lib/oracle/19.9/client64/lib <span class="nt">-L</span>/usr/lib/oracle/19.8/client/lib <span class="nt">-L</span>/usr/lib/oracle/19.8/client64/lib <span class="nt">-L</span>/usr/lib/oracle/19.6/client/lib <span class="nt">-L</span>/usr/lib/oracle/19.6/client64/lib <span class="nt">-L</span>/usr/lib/oracle/19.3/client/lib <span class="nt">-L</span>/usr/lib/oracle/19.3/client64/lib <span class="nt">-L</span>/usr/lib/oracle/18.5/client/lib <span class="nt">-L</span>/usr/lib/oracle/18.5/client64/lib <span class="nt">-L</span>/usr/lib/oracle/18.3/client/lib <span class="nt">-L</span>/usr/lib/oracle/18.3/client64/lib <span class="nt">-L</span>/usr/lib/oracle/12.2/client/lib <span class="nt">-L</span>/usr/lib/oracle/12.2/client64/lib <span class="nt">-L</span>/usr/lib/oracle/12.1/client/lib <span class="nt">-L</span>/usr/lib/oracle/12.1/client64/lib <span class="nt">-L</span>/usr/lib/oracle/11.2/client/lib <span class="nt">-L</span>/usr/lib/oracle/11.2/client64/lib <span class="nt">-L</span>/usr/lib/oracle/11.1/client/lib <span class="nt">-L</span>/usr/lib/oracle/11.1/client64/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.5/client/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.5/client64/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.4/client/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.4/client64/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.3/client/lib <span class="nt">-L</span>/usr/lib/oracle/10.2.0.3/client64/lib

postgres@servidor2:/home/postgres/oracle_fdw<span class="nv">$ </span><span class="nb">sudo </span>make <span class="nb">install</span>
/bin/mkdir <span class="nt">-p</span> <span class="s1">'/usr/lib/postgresql/11/lib'</span>
/bin/mkdir <span class="nt">-p</span> <span class="s1">'/usr/share/postgresql/11/extension'</span>
/bin/mkdir <span class="nt">-p</span> <span class="s1">'/usr/share/postgresql/11/extension'</span>
/bin/mkdir <span class="nt">-p</span> <span class="s1">'/usr/share/doc/postgresql-doc-11/extension'</span>
/usr/bin/install <span class="nt">-c</span> <span class="nt">-m</span> 755  oracle_fdw.so <span class="s1">'/usr/lib/postgresql/11/lib/oracle_fdw.so'</span>
/usr/bin/install <span class="nt">-c</span> <span class="nt">-m</span> 644 .//oracle_fdw.control <span class="s1">'/usr/share/postgresql/11/extension/'</span>
/usr/bin/install <span class="nt">-c</span> <span class="nt">-m</span> 644 .//oracle_fdw--1.2.sql .//oracle_fdw--1.0--1.1.sql .//oracle_fdw--1.1--1.2.sql  <span class="s1">'/usr/share/postgresql/11/extension/'</span>
/usr/bin/install <span class="nt">-c</span> <span class="nt">-m</span> 644 .//README.oracle_fdw <span class="s1">'/usr/share/doc/postgresql-doc-11/extension/'</span></code></pre></figure>

<p>Una vez que el resultado de la compilación ha sido correctamente instalado en los directorios locales de PostgreSQL, ya podremos hacer uso de dicha extensión. Sin embargo, en mi caso, a pesar de tener correctamente definidas las variables de entorno, el gestor no era capaz de encontrar las librerías compartidas de Oracle, de manera que ha sido necesario indicar de forma explícita la ruta a las mismas en un fichero de nombre <strong>oracle.conf</strong> dentro de <strong>/etc/ld.so.conf.d/</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres/oracle<span class="se">\_</span>fdw<span class="se">\$</span> <span class="nb">echo</span> <span class="s1">'/home/postgres/instantclient\_21\_1'</span> | <span class="nb">sudo tee</span> /etc/ld.so.conf.d/oracle.conf</code></pre></figure>

<p>Por último, tendremos que generar los enlaces necesarios y cargar en memoria las nuevas librerías compartidas que hemos introducido, de manera que ejecutaremos el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres/oracle_fdw<span class="nv">$ </span><span class="nb">sudo </span>ldconfig</code></pre></figure>

<p>Toda la configuración necesaria ha finalizado, pues únicamente faltaría generar el correspondiente enlace y empezar a hacer uso del mismo. Para ello, abriremos una <em>shell</em> de <em>psql</em> haciendo uso de la base de datos <strong>prueba2</strong> desde el usuario actual, pues es el que cuenta con los privilegios necesarios para crear dicho enlace, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor2:/home/postgres/oracle_fdw<span class="nv">$ </span>psql <span class="nt">-d</span> prueba2
psql <span class="o">(</span>11.9 <span class="o">(</span>Debian 11.9-0+deb10u1<span class="o">))</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.</code></pre></figure>

<p>Es muy importante que la conexión se realice a la base de datos <strong>prueba2</strong>, ya que es donde el rol <strong>alvaro2</strong> tiene privilegios, pues de lo contrario, no podría utilizar dicho enlace. La creación del mismo es muy sencilla, llevándose a cabo mediante la ejecución del comando:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">CREATE</span> <span class="n">EXTENSION</span> <span class="n">oracle_fdw</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="n">EXTENSION</span></code></pre></figure>

<p>La extensión que hará la función de enlace ya ha sido creada, pero para verificarlo, vamos a listar todas las extensiones existentes, haciendo para ello uso de la instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="err">\</span><span class="n">dx</span>
                                   <span class="n">List</span> <span class="k">of</span> <span class="n">installed</span> <span class="n">extensions</span>
    <span class="n">Name</span>    <span class="o">|</span> <span class="k">Version</span> <span class="o">|</span>   <span class="k">Schema</span>   <span class="o">|</span>                         <span class="n">Description</span>                          
<span class="c1">------------+---------+------------+--------------------------------------------------------------
</span>
 <span class="n">dblink</span>     <span class="o">|</span> <span class="mi">1</span><span class="p">.</span><span class="mi">2</span>     <span class="o">|</span> <span class="k">public</span>     <span class="o">|</span> <span class="k">connect</span> <span class="k">to</span> <span class="n">other</span> <span class="n">PostgreSQL</span> <span class="n">databases</span> <span class="k">from</span> <span class="n">within</span> <span class="n">a</span> <span class="k">database</span>
 <span class="n">oracle</span><span class="err">\</span><span class="n">_fdw</span> <span class="o">|</span> <span class="mi">1</span><span class="p">.</span><span class="mi">2</span>     <span class="o">|</span> <span class="k">public</span>     <span class="o">|</span> <span class="k">foreign</span> <span class="k">data</span> <span class="n">wrapper</span> <span class="k">for</span> <span class="n">Oracle</span> <span class="k">access</span>
 <span class="n">plpgsql</span>    <span class="o">|</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span>     <span class="o">|</span> <span class="n">pg</span><span class="err">\</span><span class="n">_catalog</span> <span class="o">|</span> <span class="n">PL</span><span class="o">/</span><span class="n">pgSQL</span> <span class="k">procedural</span> <span class="k">language</span>
<span class="p">(</span><span class="mi">3</span> <span class="k">rows</span><span class="p">)</span></code></pre></figure>

<p>Efectivamente, la extensión <strong>oracle_fdw</strong> ha sido correctamente generada, por lo que el siguiente paso consistirá en crear un nuevo esquema (<em>schema</em>) al que posteriormente importaremos las tablas de la base de datos Oracle remota. Para ello, utilizaremos la instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">CREATE</span> <span class="k">SCHEMA</span> <span class="n">oracle</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">SCHEMA</span></code></pre></figure>

<p>Nuestro esquema de nombre <strong>oracle</strong> ya ha sido generado, sin embargo, se encuentra actualmente vacío. Para llevar a cabo la importación de dichas tablas, tendremos que definir un nuevo servidor remoto que utilice la extensión que acabamos de generar, especificando, como es lógico, la dirección IP y la base de datos a la que pretendemos acceder, de la siguiente forma:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">CREATE</span> <span class="n">SERVER</span> <span class="n">oracle</span> <span class="k">FOREIGN</span> <span class="k">DATA</span> <span class="n">WRAPPER</span> <span class="n">oracle_fdw</span> <span class="k">OPTIONS</span> <span class="p">(</span><span class="n">dbserver</span> <span class="s1">'//192.168.1.150/ORCLCDB'</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="n">SERVER</span></code></pre></figure>

<p>Sin embargo, definir un servidor remoto no es suficiente para acceder al mismo, ya que necesitamos “mapear” nuestro usuario local a un usuario existente en dicho gestor remoto que cuente con los privilegios necesarios para visualizar las tablas.</p>

<p>En este caso, vamos a “mapear” el usuario local <strong>alvaro2</strong> al usuario remoto <strong>c##alvaro1</strong>, indicando a su vez las credenciales del mismo, ejecutando para ello la instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">CREATE</span> <span class="k">USER</span> <span class="n">MAPPING</span> <span class="k">FOR</span> <span class="n">alvaro2</span> <span class="n">SERVER</span> <span class="n">oracle</span> <span class="k">OPTIONS</span> <span class="p">(</span><span class="k">user</span> <span class="s1">'c##alvaro1'</span><span class="p">,</span> <span class="n">password</span> <span class="s1">'alvaro1'</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="k">USER</span> <span class="n">MAPPING</span></code></pre></figure>

<p>Dado que estas configuraciones las hemos llevado a cabo como un usuario administrador de la base de datos, tendremos que otorgar los privilegios necesarios al usuario <strong>alvaro2</strong> para utilizar tanto el nuevo esquema generado como el servidor remoto definido, haciendo para ello uso de las instrucciones:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">GRANT</span> <span class="k">ALL</span> <span class="k">PRIVILEGES</span> <span class="k">ON</span> <span class="k">SCHEMA</span> <span class="n">oracle</span> <span class="k">TO</span> <span class="n">alvaro2</span><span class="p">;</span>
<span class="k">GRANT</span>

<span class="n">prueba2</span><span class="o">=#</span> <span class="k">GRANT</span> <span class="k">ALL</span> <span class="k">PRIVILEGES</span> <span class="k">ON</span> <span class="k">FOREIGN</span> <span class="n">SERVER</span> <span class="n">oracle</span> <span class="k">TO</span> <span class="n">alvaro2</span><span class="p">;</span>
<span class="k">GRANT</span></code></pre></figure>

<p>La configuración como administrador ha finalizado, de manera que podremos salir del cliente ejecutando la instrucción <code class="language-plaintext highlighter-rouge">exit</code> para así realizar una nueva conexión a la base de datos <strong>prueba2</strong> pero haciendo uso esta vez del rol <strong>alvaro2</strong>, y así verificar que puede utilizar dicho enlace, ejecutando para ello el comando:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">postgres@servidor1:~<span class="nv">$ </span>psql <span class="nt">-h</span> localhost <span class="nt">-U</span> alvaro2 <span class="nt">-d</span> prueba2
Password <span class="k">for </span>user alvaro2: 
psql <span class="o">(</span>11.9 <span class="o">(</span>Debian 11.9-0+deb10u1<span class="o">))</span>
SSL connection <span class="o">(</span>protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off<span class="o">)</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.</code></pre></figure>

<p>Para verificar que el enlace funciona correctamente, vamos a proceder a importar las tablas existentes en el esquema remoto <strong>c##alvaro1</strong> a nuestro nuevo esquema local de nombre <strong>oracle</strong>, haciendo para ello uso del comando:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="n">IMPORT</span> <span class="k">FOREIGN</span> <span class="k">SCHEMA</span> <span class="nv">"C##ALVARO1"</span> <span class="k">FROM</span> <span class="n">SERVER</span> <span class="n">oracle</span> <span class="k">INTO</span> <span class="n">oracle</span><span class="p">;</span>
<span class="n">IMPORT</span> <span class="k">FOREIGN</span> <span class="k">SCHEMA</span></code></pre></figure>

<p>Listo, según la salida que dicha instrucción nos ha devuelto, el esquema ha sido correctamente importado, de manera que ya podremos hacer uso de las tablas existentes en nuestro esquema <strong>public</strong> por defecto y del nuevo que hemos generado, de nombre <strong>oracle</strong>, que contiene las tablas del gestor remoto Oracle ubicado en el servidor <strong>oracle1</strong>.</p>

<p>Es importante mencionar que en caso de ejecutar la instrucción <code class="language-plaintext highlighter-rouge">\d</code>, las tablas remotas no se mostrarían, ya que por defecto, PostgreSQL busca en el esquema por defecto <strong>public</strong>. Si quisiésemos cambiar la ruta de búsqueda al nuevo esquema generado, tendríamos que ejecutar el comando <code class="language-plaintext highlighter-rouge">SET search_path='oracle';</code>, aunque no es necesario hacerlo para consultar dichas tablas.</p>

<p>La intención es mostrar los datos de los <strong>Empleados</strong> (tabla que se encuentra en el esquema <strong>public</strong>) junto al nombre del departamento al que pertenecen, información que podremos obtener de <strong>Departamentos</strong> (tabla que se encuentra en el esquema <strong>oracle</strong>), utilizando para ello la clave que tienen en común ambas tablas. La consulta a ejecutar sería:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">SELECT</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">DNI</span> <span class="k">AS</span> <span class="n">DNI</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Nombre</span> <span class="k">AS</span> <span class="n">Nombre</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Direccion</span> <span class="k">AS</span> <span class="n">Direccion</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Telefono</span> <span class="k">AS</span> <span class="n">Telefono</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">FechaNacimiento</span> <span class="k">AS</span> <span class="n">FechaNacimiento</span><span class="p">,</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Salario</span> <span class="k">AS</span> <span class="n">Salario</span><span class="p">,</span> <span class="n">Departamentos</span><span class="p">.</span><span class="n">Nombre</span> <span class="k">AS</span> <span class="n">Departamento</span>
<span class="n">prueba2</span><span class="o">-#</span> <span class="k">FROM</span> <span class="n">oracle</span><span class="p">.</span><span class="n">Empleados</span> <span class="n">Empleados</span><span class="p">,</span> <span class="n">Departamentos</span>
<span class="n">prueba2</span><span class="o">-#</span> <span class="k">WHERE</span> <span class="n">Empleados</span><span class="p">.</span><span class="n">Departamento</span> <span class="o">=</span> <span class="n">Departamentos</span><span class="p">.</span><span class="n">Identificador</span><span class="p">;</span>
    <span class="n">dni</span>    <span class="o">|</span>          <span class="n">nombre</span>           <span class="o">|</span>       <span class="n">direccion</span>        <span class="o">|</span> <span class="n">telefono</span>  <span class="o">|</span>   <span class="n">fechanacimiento</span>   <span class="o">|</span> <span class="n">salario</span> <span class="o">|</span>   <span class="n">departamento</span>   
<span class="c1">-----------+---------------------------+------------------------+-----------+---------------------+---------+------------------
</span>
 <span class="mi">90389058</span><span class="n">R</span> <span class="o">|</span> <span class="n">Joaquin</span> <span class="n">Marrero</span> <span class="n">Covas</span>     <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Hijuela</span> <span class="n">de</span> <span class="n">Lojo</span><span class="p">,</span> <span class="mi">22</span> <span class="o">|</span> <span class="mi">618385118</span> <span class="o">|</span> <span class="mi">1997</span><span class="o">-</span><span class="mi">01</span><span class="o">-</span><span class="mi">28</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">1446</span> <span class="o">|</span> <span class="n">Administraci</span><span class="err">ó</span><span class="n">n</span>
 <span class="mi">18232747</span><span class="n">A</span> <span class="o">|</span> <span class="n">Aristarco</span> <span class="n">Caban</span> <span class="n">Meraz</span>     <span class="o">|</span> <span class="n">Puerta</span> <span class="n">Nueva</span><span class="p">,</span> <span class="mi">67</span>       <span class="o">|</span> <span class="mi">691204722</span> <span class="o">|</span> <span class="mi">1994</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">07</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">4789</span> <span class="o">|</span> <span class="n">Seguridad</span>
 <span class="mi">94106513</span><span class="n">N</span> <span class="o">|</span> <span class="n">Marian</span> <span class="n">Fonseca</span> <span class="n">Betancourt</span> <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Manuel</span> <span class="n">Iradier</span><span class="p">,</span> <span class="mi">37</span>  <span class="o">|</span> <span class="mi">638415823</span> <span class="o">|</span> <span class="mi">1979</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">17</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">2561</span> <span class="o">|</span> <span class="n">Seguridad</span>
 <span class="mi">12777631</span><span class="k">G</span> <span class="o">|</span> <span class="n">Merlino</span> <span class="n">Rosado</span> <span class="n">Cordero</span>    <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Henan</span> <span class="n">Cortes</span><span class="p">,</span> <span class="mi">58</span>    <span class="o">|</span> <span class="mi">609841755</span> <span class="o">|</span> <span class="mi">1993</span><span class="o">-</span><span class="mi">11</span><span class="o">-</span><span class="mi">27</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">7961</span> <span class="o">|</span> <span class="n">Recursos</span> <span class="n">Humanos</span>
 <span class="mi">68219319</span><span class="n">P</span> <span class="o">|</span> <span class="n">Tabare</span> <span class="n">Chapa</span> <span class="n">Alcantar</span>     <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Arana</span><span class="p">,</span> <span class="mi">12</span>           <span class="o">|</span> <span class="mi">682227206</span> <span class="o">|</span> <span class="mi">1992</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">09</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">8568</span> <span class="o">|</span> <span class="n">Inform</span><span class="err">á</span><span class="n">tica</span>
 <span class="mi">67227129</span><span class="n">S</span> <span class="o">|</span> <span class="n">Ian</span> <span class="n">Esquivel</span> <span class="n">Laboy</span>        <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Inglaterra</span><span class="p">,</span> <span class="mi">64</span>      <span class="o">|</span> <span class="mi">728005136</span> <span class="o">|</span> <span class="mi">1992</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">21</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">3519</span> <span class="o">|</span> <span class="n">Administraci</span><span class="err">ó</span><span class="n">n</span>
 <span class="mi">52315160</span><span class="k">G</span> <span class="o">|</span> <span class="n">Heinz</span> <span class="n">Collado</span> <span class="n">Caraballo</span>   <span class="o">|</span> <span class="n">Escuadro</span><span class="p">,</span> <span class="mi">60</span>           <span class="o">|</span> <span class="mi">600173822</span> <span class="o">|</span> <span class="mi">1984</span><span class="o">-</span><span class="mi">02</span><span class="o">-</span><span class="mi">13</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">1672</span> <span class="o">|</span> <span class="n">Recursos</span> <span class="n">Humanos</span>
 <span class="mi">85145590</span><span class="k">G</span> <span class="o">|</span> <span class="n">Anabel</span> <span class="n">Lerma</span> <span class="n">Dominguez</span>    <span class="o">|</span> <span class="n">Crta</span><span class="p">.</span> <span class="n">Cadiz</span><span class="p">,</span> <span class="mi">1</span>         <span class="o">|</span> <span class="mi">675014823</span> <span class="o">|</span> <span class="mi">1981</span><span class="o">-</span><span class="mi">01</span><span class="o">-</span><span class="mi">07</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">5919</span> <span class="o">|</span> <span class="n">Administraci</span><span class="err">ó</span><span class="n">n</span>
 <span class="mi">56228957</span><span class="n">Y</span> <span class="o">|</span> <span class="n">Dinorah</span> <span class="n">Viera</span> <span class="n">Tello</span>       <span class="o">|</span> <span class="n">Ctra</span><span class="p">.</span> <span class="n">Villena</span><span class="p">,</span> <span class="mi">22</span>      <span class="o">|</span> <span class="mi">642852778</span> <span class="o">|</span> <span class="mi">1987</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">28</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">2567</span> <span class="o">|</span> <span class="n">Seguridad</span>
 <span class="mi">61242562</span><span class="n">W</span> <span class="o">|</span> <span class="n">Manases</span> <span class="n">Castillo</span> <span class="n">Camacho</span>  <span class="o">|</span> <span class="n">Ctra</span><span class="p">.</span> <span class="n">Hornos</span><span class="p">,</span> <span class="mi">91</span>       <span class="o">|</span> <span class="mi">607853354</span> <span class="o">|</span> <span class="mi">1991</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">29</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">4270</span> <span class="o">|</span> <span class="n">Inform</span><span class="err">á</span><span class="n">tica</span>
<span class="p">(</span><span class="mi">10</span> <span class="k">rows</span><span class="p">)</span></code></pre></figure>

<p>Donde:</p>

<ul>
  <li><strong>SELECT</strong>: Indicamos las columnas que queremos mostrar de la información obtenida, de la forma <strong>[tabla].[columna]</strong>.</li>
  <li><strong>FROM</strong>: Hacemos un JOIN de la tabla Empleados que se encuentra en el esquema <strong>public</strong> junto con la tabla Departamentos que se encuentra en el esquema <strong>oracle</strong>, de la forma <strong>[esquema].[tabla]</strong>, para así poder mostrar información de ambas tablas en la misma consulta.</li>
  <li><strong>WHERE</strong>: Establecemos la condición del JOIN, que deberá ser aquella columna mediante la cual vamos a unir los registros devueltos. Como es lógico, será el código del departamento, pues es la columna que se repite en ambas tablas.</li>
</ul>

<p>Como era de esperar, la consulta ha vuelto a realizarse sin ningún problema y ha devuelto la información que debería.</p>

<p>Otra utilidad que podemos aprovechar es la capacidad de copiar las tablas de un esquema a otro, utilizando el resultado de una consulta simple para crear una tabla a partir de la misma. Por ejemplo, podríamos copiar la tabla <strong>Empleados</strong> desde el esquema <strong>oracle</strong> haciendo uso de la siguiente instrucción:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Empleados</span>
<span class="n">prueba2</span><span class="o">-#</span> <span class="k">AS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="o">*</span>
<span class="n">prueba2</span><span class="p">(</span><span class="o">#</span>     <span class="k">FROM</span> <span class="n">oracle</span><span class="p">.</span><span class="n">Empleados</span><span class="p">);</span>
<span class="k">SELECT</span> <span class="mi">10</span></code></pre></figure>

<p>En dicha instrucción, hemos realizado una consulta a la tabla <strong>Empleados</strong> ubicada en el esquema <strong>oracle</strong> generado a partir de la base de datos existente en el servidor <strong>oracle1</strong>, utilizando la respuesta obtenida para crear una nueva tabla con el mismo nombre, que se almacenará ahora en el esquema <strong>public</strong>, y que podremos empezar a utilizar sin necesidad de recurrir al enlace con el primer servidor. Si consultamos la nueva tabla generada, obtendremos el siguiente resultado:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">prueba2</span><span class="o">=#</span> <span class="k">SELECT</span> <span class="o">*</span>
<span class="n">prueba2</span><span class="o">-#</span> <span class="k">FROM</span> <span class="n">Empleados</span><span class="p">;</span>
    <span class="n">dni</span>    <span class="o">|</span>          <span class="n">nombre</span>           <span class="o">|</span>       <span class="n">direccion</span>        <span class="o">|</span> <span class="n">telefono</span>  <span class="o">|</span>   <span class="n">fechanacimiento</span>   <span class="o">|</span> <span class="n">salario</span> <span class="o">|</span> <span class="n">departamento</span> 
<span class="c1">-----------+---------------------------+------------------------+-----------+---------------------+---------+--------------
</span>
 <span class="mi">90389058</span><span class="n">R</span> <span class="o">|</span> <span class="n">Joaquin</span> <span class="n">Marrero</span> <span class="n">Covas</span>     <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Hijuela</span> <span class="n">de</span> <span class="n">Lojo</span><span class="p">,</span> <span class="mi">22</span> <span class="o">|</span> <span class="mi">618385118</span> <span class="o">|</span> <span class="mi">1997</span><span class="o">-</span><span class="mi">01</span><span class="o">-</span><span class="mi">28</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">1446</span> <span class="o">|</span>           <span class="mi">10</span>
 <span class="mi">18232747</span><span class="n">A</span> <span class="o">|</span> <span class="n">Aristarco</span> <span class="n">Caban</span> <span class="n">Meraz</span>     <span class="o">|</span> <span class="n">Puerta</span> <span class="n">Nueva</span><span class="p">,</span> <span class="mi">67</span>       <span class="o">|</span> <span class="mi">691204722</span> <span class="o">|</span> <span class="mi">1994</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">07</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">4789</span> <span class="o">|</span>           <span class="mi">30</span>
 <span class="mi">94106513</span><span class="n">N</span> <span class="o">|</span> <span class="n">Marian</span> <span class="n">Fonseca</span> <span class="n">Betancourt</span> <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Manuel</span> <span class="n">Iradier</span><span class="p">,</span> <span class="mi">37</span>  <span class="o">|</span> <span class="mi">638415823</span> <span class="o">|</span> <span class="mi">1979</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">17</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">2561</span> <span class="o">|</span>           <span class="mi">30</span>
 <span class="mi">12777631</span><span class="k">G</span> <span class="o">|</span> <span class="n">Merlino</span> <span class="n">Rosado</span> <span class="n">Cordero</span>    <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Henan</span> <span class="n">Cortes</span><span class="p">,</span> <span class="mi">58</span>    <span class="o">|</span> <span class="mi">609841755</span> <span class="o">|</span> <span class="mi">1993</span><span class="o">-</span><span class="mi">11</span><span class="o">-</span><span class="mi">27</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">7961</span> <span class="o">|</span>           <span class="mi">20</span>
 <span class="mi">68219319</span><span class="n">P</span> <span class="o">|</span> <span class="n">Tabare</span> <span class="n">Chapa</span> <span class="n">Alcantar</span>     <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Arana</span><span class="p">,</span> <span class="mi">12</span>           <span class="o">|</span> <span class="mi">682227206</span> <span class="o">|</span> <span class="mi">1992</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">09</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">8568</span> <span class="o">|</span>           <span class="mi">40</span>
 <span class="mi">67227129</span><span class="n">S</span> <span class="o">|</span> <span class="n">Ian</span> <span class="n">Esquivel</span> <span class="n">Laboy</span>        <span class="o">|</span> <span class="k">C</span><span class="o">/</span> <span class="n">Inglaterra</span><span class="p">,</span> <span class="mi">64</span>      <span class="o">|</span> <span class="mi">728005136</span> <span class="o">|</span> <span class="mi">1992</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">21</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">3519</span> <span class="o">|</span>           <span class="mi">10</span>
 <span class="mi">52315160</span><span class="k">G</span> <span class="o">|</span> <span class="n">Heinz</span> <span class="n">Collado</span> <span class="n">Caraballo</span>   <span class="o">|</span> <span class="n">Escuadro</span><span class="p">,</span> <span class="mi">60</span>           <span class="o">|</span> <span class="mi">600173822</span> <span class="o">|</span> <span class="mi">1984</span><span class="o">-</span><span class="mi">02</span><span class="o">-</span><span class="mi">13</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">1672</span> <span class="o">|</span>           <span class="mi">20</span>
 <span class="mi">85145590</span><span class="k">G</span> <span class="o">|</span> <span class="n">Anabel</span> <span class="n">Lerma</span> <span class="n">Dominguez</span>    <span class="o">|</span> <span class="n">Crta</span><span class="p">.</span> <span class="n">Cadiz</span><span class="p">,</span> <span class="mi">1</span>         <span class="o">|</span> <span class="mi">675014823</span> <span class="o">|</span> <span class="mi">1981</span><span class="o">-</span><span class="mi">01</span><span class="o">-</span><span class="mi">07</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">5919</span> <span class="o">|</span>           <span class="mi">10</span>
 <span class="mi">56228957</span><span class="n">Y</span> <span class="o">|</span> <span class="n">Dinorah</span> <span class="n">Viera</span> <span class="n">Tello</span>       <span class="o">|</span> <span class="n">Ctra</span><span class="p">.</span> <span class="n">Villena</span><span class="p">,</span> <span class="mi">22</span>      <span class="o">|</span> <span class="mi">642852778</span> <span class="o">|</span> <span class="mi">1987</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">28</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">2567</span> <span class="o">|</span>           <span class="mi">30</span>
 <span class="mi">61242562</span><span class="n">W</span> <span class="o">|</span> <span class="n">Manases</span> <span class="n">Castillo</span> <span class="n">Camacho</span>  <span class="o">|</span> <span class="n">Ctra</span><span class="p">.</span> <span class="n">Hornos</span><span class="p">,</span> <span class="mi">91</span>       <span class="o">|</span> <span class="mi">607853354</span> <span class="o">|</span> <span class="mi">1991</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">29</span> <span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="o">|</span>    <span class="mi">4270</span> <span class="o">|</span>           <span class="mi">40</span>
<span class="p">(</span><span class="mi">10</span> <span class="k">rows</span><span class="p">)</span></code></pre></figure>

<p>Como se puede apreciar, el contenido es exactamente el mismo que el existente en la tabla ubicada en el gestor remoto, por lo que podemos concluir que su clonación ha sido efectiva y que los servidores tienen conectividad entre sí mediante los enlaces creados, sea cual sea el sentido utilizado.</p>]]></content><author><name>Álvaro Vaca Ferreras</name></author><category term="bbdd" /><summary type="html"><![CDATA[El objetivo de este post es el de mostrar el procedimiento a seguir para llevar a cabo una interconexión entre dos servidores Oracle 19c y PostgreSQL alojados en máquinas CentOS 8 y Debian Buster, respectivamente, siendo su finalidad la de permitir el acceso desde un mismo cliente a dos bases de datos ubicadas en gestores completamente distintos, de una forma simultánea pero indirecta, de manera que uno de los servidores actuará como cliente del otro servidor, de manera unidireccional. Al fin y al cabo, el cliente únicamente abre una conexión, pues es el primer servidor al que se conecta el que posteriormente abre una conexión al segundo de ellos. Para esta ocasión, voy a reutilizar dos de las máquinas virtuales creadas en los artículos Interconexión de Oracle 19c en CentOS 8 e Instalación e interconexión de PostgreSQL en Debian 10, por lo que se recomienda la previa lectura de dichos posts, pues en este artículo se tratarán con menor profundidad u obviarán algunos aspectos vistos con anterioridad. Las máquinas actualmente existentes en el escenario son las siguientes: oracle1: Máquina CentOS 8 con Oracle 19c, conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.150. servidor2: Máquina Debian Buster con PostgreSQL, conectada a mi red doméstica en modo puente (bridge), con dirección IP asignada 192.168.1.161. Oracle 19c a PostgreSQL Partimos del punto en el que tanto el servidor Oracle 19c como el PostgreSQL están totalmente operativos y listos para escuchar peticiones remotas. Como en anteriores artículos hemos visto, los servidores Oracle cuentan con soporte nativo para realizar enlaces con otros servidores Oracle, sin embargo, no ocurre lo mismo cuando deseamos conectarnos a otro gestor totalmente diferente, como puede ser PostgreSQL. En estos casos, podemos acudir a ODBC (Open Database Connectivity), un estándar de acceso a las bases de datos cuyo objetivo es hacer posible el acceso a cualquier dato desde cualquier aplicación, sin importar qué sistema de gestión de bases de datos almacene los datos. En este caso, vamos a configurar un enlace a una base de datos PostgreSQL, sin embargo, es posible realizar una integración con muchos otros gestores como MySQL e incluso gestores no relacionales como Excel. El primer paso consistirá en instalar la paquetería necesaria para crear dicho enlace, concretamente el paquete unixODBC, que es el proyecto de código abierto que implementa la API ODBC de la que previamente hemos hablado. De otro lado, dado que pretendemos crear un enlace con un servidor PostgreSQL, tendremos que instalar el driver específico para ello, de nombre postgresql-odbc, no sin antes asegurar que toda la paquetería instalada en la máquina se encuentra en su última versión, por lo que ejecutaremos el comando: [root@oracle1 ~]# dnf update &amp;&amp; dnf install unixODBC postgresql-odbc Cuando la instalación de los paquetes haya finalizado, procederemos a modificar el fichero /etc/odbcinst.ini, en el que encontraremos la configuración de todos los drivers de ODBC existentes, haciendo para ello uso del comando: [root@oracle1 ~]# nano /etc/odbcinst.ini De todos los drivers existentes, el único que nos interesa es el referente a PostgreSQL, que tendrá la siguiente forma: [PostgreSQL] Description = ODBC for PostgreSQL Driver = /usr/lib/psqlodbcw.so Setup = /usr/lib/libodbcpsqlS.so Driver64 = /usr/lib64/psqlodbcw.so Setup64 = /usr/lib64/libodbcpsqlS.so FileUsage = 1 Como se puede apreciar, la definición del mismo se encuentra compuesta por una breve descripción y las rutas a las diferentes librerías que lo constituyen. En mi caso, he comentado la definición del resto de drivers, ya que para este caso no me van a ser necesarios. El siguiente paso consistirá en crear un DSN (Data Source Name) en el fichero /etc/odbc.ini, pues dicho fichero será posteriormente utilizado para determinar la manera de conectarse al gestor especificado. Para ello, ejecutaremos el comando: [root@oracle1 ~]# nano /etc/odbc.ini Dentro del mismo, introduciremos el siguiente contenido: [PSQLU] Debug = 0 CommLog = 0 ReadOnly = 0 Driver = PostgreSQL Servername = 192.168.1.161 Username = alvaro2 Password = alvaro2 Port = 5432 Database = prueba2 Trace = 0 TraceFile = /tmp/sql.log Donde: Driver: Indicamos el nombre del driver previamente configurado en el fichero /etc/odbcinst.ini. En este caso, PostgreSQL. Servername: Indicamos la dirección IP del servidor PostgreSQL al que deseamos crear el enlace. En este caso, 192.168.1.161. Username: Indicamos el nombre del usuario para el acceso a la base de datos remota. En este caso, alvaro2. Password: Indicamos la contraseña del usuario para el acceso a la base de datos remota. En este caso, alvaro2. Port: Indicamos el puerto en el que está escuchando peticiones el servidor PostgreSQL. En este caso, 5432. Database: Indicamos el nombre de la base de datos remota a la que pretendemos conectarnos. En este caso, prueba2. La configuración del driver de ODBC ha finalizado, de manera que es aconsejable hacer una pequeña prueba llegado este punto para verificar su correcto funcionamiento, haciendo para ello uso del binario isql incluido en el paquete previamente instalado, seguido del DSN en cuestión. Gracias al mismo, podremos realizar una conexión ODBC para así comprobar que es posible acceder a la base de datos PostgreSQL remota haciendo uso de los parámetros que hemos especificado, ejecutando para ello el comando: [root@oracle1 ~]# isql PSQLU +---------------------------------------+ | Connected! | | | | sql-statement | | help [tablename] | | quit | | | +---------------------------------------+ Como se puede apreciar en la salida del comando ejecutado, la conexión ha sido exitosa y actualmente nos encontramos haciendo uso de un cliente en el que podríamos ejecutar órdenes SQL, como por ejemplo listar el contenido de la tabla Departamentos, haciendo para ello uso de la instrucción: SQL&gt; SELECT * FROM Departamentos; +--------------+---------------------+----------------+ | identificador| nombre | localizacion | +--------------+---------------------+----------------+ | 10 | Administración | Sevilla | | 20 | Recursos Humanos | Barcelona | | 30 | Seguridad | Madrid | | 40 | Informática | Valencia | +--------------+---------------------+----------------+ SQLRowCount returns 4 4 rows fetched Efectivamente, la información devuelta concuerda con la almacenada en dicha tabla remota, de manera que podremos salir del cliente ejecutando la instrucción exit para así continuar con la configuración del enlace. Como anteriormente hemos mencionado, la configuración del driver ha finalizado, sin embargo, Oracle no está todavía configurado para poder utilizar dicho driver, por lo que el siguiente paso consistirá en generar un fichero en el que se especifiquen determinados parámetros necesarios para ello (Heterogeneous Services). La ubicación de dicho fichero será $ORACLE_HOME/hs/admin/ y su nombre, init[DSN].ora, de manera que en este caso, al ser PSQLU el DSN, el nombre del mismo sería initPSQLU.ora. Para generar y editar dicho fichero, haremos uso del comando: [root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/hs/admin/initPSQLU.ora Dentro del mismo, introduciremos el siguiente contenido: HS_FDS_CONNECT_INFO = PSQLU HS_FDS_TRACE_LEVEL = DEBUG HS_FDS_SHAREABLE_NAME = /usr/lib64/psqlodbcw.so HS_LANGUAGE = AMERICAN_AMERICA.WE8ISO8859P1 set ODBCINI=/etc/odbc.ini Como se puede apreciar, dentro del mismo hemos definido determinados parámetros como el nombre del DSN, el driver que debe utilizar que habrá sido previamente especificado en el fichero /etc/odbcinst.ini, el fichero en el que se encuentran definidos los DSN… Tras ello, todo estará listo para comenzar a configurar el listener, que para quién no conozca lo que es, es el proceso encargado de escuchar las peticiones entrantes en el lado del servidor, cuyo fichero en el que se define su configuración se encuentra almacenado en $ORACLE_HOME/network/admin/, con el nombre listener.ora, estableciéndose en el mismo configuración entre la que se encuentran las bases de datos para las que va a escuchar peticiones y los puertos en los que va a hacerlo, por lo que procederemos a modificar dicho fichero ejecutando para ello el comando: [root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/network/admin/listener.ora En este caso, tendremos que añadir una nueva entrada que habilitará la escucha hacia el driver ODBC, especificando para ello el DSN, la ruta del directorio principal de Oracle y el programa en cuestión, quedando en mi caso de la siguiente forma: LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = oracle1.alvarovf.com)(PORT = 1521)) (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521)) ) ) SID_LIST_LISTENER= (SID_LIST= (SID_DESC= (SID_NAME=PSQLU) (ORACLE_HOME=/opt/oracle/product/19c/dbhome_1) (PROGRAM=dg4odbc) ) ) El siguiente paso será modificar el fichero de nombre tnsnames.ora, ubicado en $ORACLE_HOME/network/admin/, que permite facilitar la tarea de acceso a servidores remotos, indicando en el mismo una entrada por cada uno de los servidores a los que se pretende acceder, que en este caso nos servirá para “mapear” la conexión hacia el driver ODBC. Su configuración no es para nada complicada, así que vamos a llevarla a cabo ejecutando para ello el comando: [root@oracle1 ~]# nano /opt/oracle/product/19c/dbhome_1/network/admin/tnsnames.ora En dicha entrada tendremos que incluir el protocolo, la dirección IP y el puerto (que como es lógico corresponden a la propia máquina local), así como un alias que será el que se utilice a la hora de la conexión, que en este caso coincidirá con el DSN asignado, quedando en mi caso de la siguiente forma: ORCLCDB = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ORCLCDB) ) ) LISTENER_ORCLCDB = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) ORACLE2 = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.151)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ORCLCDB) ) ) PSQLU = (DESCRIPTION= (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)) (CONNECT_DATA=(SID=PSQLU)) (HS=OK) ) Tras ello, simplemente guardaremos los cambios realizados y todo estará listo para reiniciar el proceso del listener, con la intención de que cargue en memoria la nueva configuración asignada, cambiándonos previamente al usuario oracle, pues es el único que tiene actualmente definida en sus variables de entorno la ruta a los binarios de Oracle, ejecutando por tanto el comando: [root@oracle1 ~]# su - oracle Cuando nos encontremos haciendo uso del usuario oracle, podremos llevar a cabo dicho reinicio, haciendo para ello uso de los comandos: [oracle@oracle1 ~]$ lsnrctl stop [oracle@oracle1 ~]$ lsnrctl start Una vez que el listener haya sido correctamente reiniciado, es momento de abrir una shell de sqlplus haciendo uso del usuario c##alvaro1 actualmente existente en el gestor Oracle, ejecutando para ello el comando: [oracle@oracle1 ~]$ sqlplus c##alvaro1/alvaro1 SQL*Plus: Release 19.0.0.0.0 - Production on Thu Feb 4 12:05:36 2021 Version 19.3.0.0.0 Copyright (c) 1982, 2019, Oracle. All rights reserved. Connected to an idle instance. Por último, tendremos que llevar a cabo la creación del enlace, que será muy sencilla, ya que previamente hemos definido los parámetros de la conexión en el fichero tnsnames.ora, llevándose a cabo mediante la ejecución del comando: SQL&gt; CREATE DATABASE LINK postgreslink CONNECT TO "alvaro2" IDENTIFIED BY "alvaro2" USING 'PSQLU'; Enlace con la base de datos creado. Donde: CREATE DATABASE LINK: Especificamos un nombre identificativo para el enlace. CONNECT TO: Indicamos las credenciales de acceso a la base de datos remota. USING: Indicamos el nombre del alias de la conexión que previamente hemos definido en el fichero tnsnames.ora. Nota: Es importante utilizar comillas dobles (” “) para el nombre de usuario y la contraseña, pero comillas simples (’ ‘) para el nombre del alias. Efectivamente, el enlace postgreslink ha sido correctamente generado, de manera que vamos a proceder a verificar el funcionamiento de dicho enlace, ejecutando para ello una consulta cuya información mostrada provenga de ambas bases de datos, ubicadas como ya hemos visto, en servidores distintos. La intención es mostrar los datos de los Empleados (tabla que se encuentra en oracle1) junto al nombre del departamento al que pertenecen, información que podremos obtener de Departamentos (tabla que se encuentra en servidor2), utilizando para ello la clave que tienen en común ambas tablas. La consulta a ejecutar sería: SQL&gt; SELECT Empleados.DNI AS DNI, Empleados.Nombre AS Nombre, Empleados.Direccion AS Direccion, Empleados.Telefono AS Telefono, Empleados.FechaNacimiento AS FechaNacimiento, Empleados.Salario AS Salario, Departamentos."nombre" AS Departamento FROM Empleados, "departamentos"@postgreslink Departamentos WHERE Empleados.Departamento = Departamentos."identificador"; DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- 90389058R Joaquin Marrero Covas C/ Hijuela de Lojo, 22 618385118 28/01/97 1446 Administraci??n 67227129S Ian Esquivel Laboy C/ Inglaterra, 64 728005136 21/07/92 3519 Administraci??n DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- 85145590G Anabel Lerma Dominguez Crta. Cadiz, 1 675014823 07/01/81 5919 Administraci??n 12777631G Merlino Rosado Cordero C/ Henan Cortes, 58 609841755 27/11/93 7961 DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- Recursos Humanos 52315160G Heinz Collado Caraballo Escuadro, 60 600173822 13/02/84 1672 Recursos Humanos 18232747A Aristarco Caban Meraz Puerta Nueva, 67 691204722 DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- 07/05/94 4789 Seguridad 94106513N Marian Fonseca Betancourt C/ Manuel Iradier, 37 638415823 17/07/79 2561 Seguridad DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- 56228957Y Dinorah Viera Tello Ctra. Villena, 22 642852778 28/05/87 2567 Seguridad 68219319P Tabare Chapa Alcantar C/ Arana, 12 682227206 09/03/92 8568 Inform?!tica DNI NOMBRE DIRECCION TELEFONO --------- ------------------------------ ------------------------- --------- FECHANAC SALARIO -------- ---------- DEPARTAMENTO -------------------------------------------------------------------------------- 61242562W Manases Castillo Camacho Ctra. Hornos, 91 607853354 29/10/91 4270 Inform?!tica 10 filas seleccionadas. Donde: SELECT: Indicamos las columnas que queremos mostrar de la información obtenida, de la forma [tabla].[columna]. FROM: Hacemos un JOIN de la tabla Empleados que se encuentra en la base de datos local junto a la consulta remota, haciendo uso del enlace que acabamos de generar, para así poder mostrar información de ambas tablas en la misma consulta. WHERE: Establecemos la condición del JOIN, que deberá ser aquella columna mediante la cual vamos a unir los registros devueltos. Como es lógico, será el código del departamento, pues es la columna que se repite en ambas tablas. Nota: Es importante utilizar comillas dobles (” “) para el nombre de las tablas y las columnas siempre que se haga referencia al gestor PostgreSQL, indicando el nombre de las mismas en minúsculas. Al parecer, la consulta se ha realizado sin ningún problema y ha devuelto la información que debería, pues tal y como he mencionado con anterioridad, ambas máquinas servidoras cuentan con un direccionamiento dentro de la red local, siendo ambas totalmente alcanzables entre sí, además de estar correctamente configuradas para aceptar dichas conexiones. Otra utilidad que estos enlaces nos aportan es la capacidad de copiar las tablas de un gestor a otro, utilizando el resultado de una consulta simple para crear una tabla a partir de la misma. Por ejemplo, podríamos copiar la tabla Departamentos haciendo uso de la siguiente instrucción: SQL&gt; CREATE TABLE Departamentos AS (SELECT * FROM "departamentos"@postgreslink); Tabla creada. En dicha instrucción, hemos realizado una consulta a la tabla Departamentos ubicada en la base de datos del servidor servidor2, utilizando la respuesta obtenida para crear una nueva tabla con el mismo nombre, que se almacenará ahora de forma local en el servidor oracle1, y que podremos empezar a utilizar sin necesidad de recurrir al enlace con el segundo servidor. Si consultamos la nueva tabla generada, obtendremos el siguiente resultado: SQL&gt; SELECT * 2 FROM Departamentos; identificador ------------- nombre -------------------------------------------------------------------------------- localizacion -------------------------------------------------------------------------------- 10 Administraci??n Sevilla 20 Recursos Humanos Barcelona identificador ------------- nombre -------------------------------------------------------------------------------- localizacion -------------------------------------------------------------------------------- 30 Seguridad Madrid 40 Inform?!tica identificador ------------- nombre -------------------------------------------------------------------------------- localizacion -------------------------------------------------------------------------------- Valencia Como se puede apreciar, el contenido es exactamente el mismo que el existente en la tabla ubicada en el gestor remoto, por lo que podemos concluir que su clonación ha sido efectiva. El enlace ha funcionado del extremo oracle1 al extremo servidor2, pero como ya sabemos, dichos enlaces son unidireccionales, de manera que si quisiésemos realizar la conexión a la inversa, de PostgreSQL a Oracle, tendríamos que llevar a cabo un proceso un tanto distinto, así que vamos a proceder a ello. PostgreSQL a Oracle 19c Una vez más, partimos del punto en el que tanto el servidor PostgreSQL como el Oracle 19c están totalmente operativos y listos para escuchar peticiones remotas. Como en anteriores artículos hemos visto, los servidores PostgreSQL cuentan con soporte nativo para realizar enlaces con otros servidores PostgreSQL, sin embargo, no ocurre lo mismo cuando deseamos conectarnos a otro gestor totalmente diferente, como puede ser Oracle. En estos casos, podemos acudir a oracle_fdw (Foreign Data Wrapper for Oracle), un concepto que introdujo PostgreSQL hace varias versiones cuyo objetivo es hacer posible el acceso de una forma muy sencilla y eficiente a las bases de datos Oracle, tal y como mostraremos a continuación. Existen otros Data Wrappers, que posibilitan una integración con muchos otros gestores como MySQL e incluso gestores no relacionales como MongoDB. El paquete oracle_fdw no se encuentra actualmente disponible en los repositorios oficiales de CentOS 8, sino que tendremos que compilarlo a mano. Por ese mismo motivo, necesitaremos instalar una serie de paquetes que nos proporcionarán las herramientas necesarias para llevar a cabo dicha compilación, así como los paquetes oficiales de Oracle Instant Client. Comenzaremos por la instalación de aquellos paquetes esenciales para la compilación, no sin antes actualizar la lista de la paquetería disponible, por lo que haremos uso del comando: root@servidor2:/home/postgres# apt update &amp;&amp; apt install libaio1 postgresql-server-dev-all build-essential git Donde: libaio1: Biblioteca activa en el espacio de usuario importante para el rendimiento de las bases de datos y otras aplicaciones avanzadas. postgresql-server-dev-all: Proporciona las herramientas necesarias para la compilación de extensiones para PostgreSQL. build-essential: Como su propio nombre indica, contiene las utilidades esenciales para compilar un paquete en Debian (compiladores y bibliotecas). git: Nos permitirá clonar el repositorio GitHub en el que está contenido el código fuente de la aplicación. Una vez instalados los paquetes esenciales, tendremos que descargar también los paquetes oficiales de Oracle Instant Client, que nos permitirán hacer uso del cliente Oracle para llevar a cabo las conexiones a las bases de datos remotas. Antes de ello, vamos a cambiarnos al usuario postgres, pues será aquel que utilizará principalmente dichos binarios, ejecutando para ello el comando: root@servidor2:/home/postgres# su postgres Los paquetes que debemos descargar del sitio oficial de Oracle son los siguientes: instantclient-basic-linux.x64-21.1.0.0.0.zip instantclient-sdk-linux.x64-21.1.0.0.0.zip instantclient-sqlplus-linux.x64-21.1.0.0.0.zip Para llevar a cabo la descarga de dichos paquetes, haremos uso de los comandos: postgres@servidor2:/home/postgres$ wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-basic-linux.x64-21.1.0.0.0.zip postgres@servidor2:/home/postgres$ wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sdk-linux.x64-21.1.0.0.0.zip postgres@servidor2:/home/postgres$ wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sqlplus-linux.x64-21.1.0.0.0.zip Tras ello, vamos a listar el contenido del directorio actual para verificar que las descargas se han completado satisfactoriamente: postgres@servidor2:/home/postgres$ ls -l total 79288 -rw-r--r-- 1 postgres postgres 79250994 Dec 1 12:07 instantclient-basic-linux.x64-21.1.0.0.0.zip -rw-r--r-- 1 postgres postgres 998327 Dec 1 12:07 instantclient-sdk-linux.x64-21.1.0.0.0.zip -rw-r--r-- 1 postgres postgres 936169 Dec 1 12:07 instantclient-sqlplus-linux.x64-21.1.0.0.0.zip Efectivamente, se han descargado los siguientes ficheros: instantclient-basic-linux.x64-21.1.0.0.0.zip de peso total 75.58M (79250994 bytes). instantclient-sdk-linux.x64-21.1.0.0.0.zip de peso total 974.93K (998327 bytes). instantclient-sqlplus-linux.x64-21.1.0.0.0.zip de peso total 914.23K (936169 bytes). Si nos fijamos, los paquetes están comprimidos, por lo que no podremos utilizar los binarios incluidos hasta que no hagamos una extracción de los mismos. Además, para liberar espacio, borraremos tras ello los ficheros comprimidos ya que no nos harán falta. Para descomprimir un paquete .zip haremos uso de unzip, ejecutando los comandos: postgres@servidor2:/home/postgres$ unzip instantclient-basic-linux.x64-21.1.0.0.0.zip postgres@servidor2:/home/postgres$ unzip instantclient-sqlplus-linux.x64-21.1.0.0.0.zip postgres@servidor2:/home/postgres$ unzip instantclient-sdk-linux.x64-21.1.0.0.0.zip postgres@servidor2:/home/postgres$ rm *.zip Para verificar que los ficheros se han descomprimido y eliminado correctamente, haremos uso del comando: postgres@servidor2:/home/postgres$ ls -l total 4 drwxr-xr-x 4 postgres postgres 4096 Feb 4 11:28 instantclient_21_1 Efectivamente, todo el contenido se ha descomprimido tal y como queríamos en un directorio de nombre instantclient_21_1, que contendrá todos los binarios que nos ofrece un cliente Oracle, entre los que se encuentra nuestro preciado sqlplus. Para verificarlo, podemos listar los ejecutables de dicho directorio, estableciendo un filtro por nombre para no mostrar las librerías compartidas, ejecutando para ello el comando: postgres@servidor2:/home/postgres$ find instantclient_21_1/ -executable -type f | egrep -v '.so.*$' instantclient_21_1/sqlplus instantclient_21_1/sdk/ott instantclient_21_1/uidrvci instantclient_21_1/adrci instantclient_21_1/genezi Sin embargo, si ahora mismo quisiésemos hacer uso de cualquiera de esos binarios, tendríamos que indicar la ruta completa para que el sistema sea capaz de encontrarlo. Esto tiene una razón, y es que en Linux existe una variable de entorno de nombre PATH ($PATH) que indica las rutas en las que buscar los binarios (y en qué orden), en la que no se encuentra actualmente especificada la ruta /home/postgres/instantclient_21_1. Para solucionar esto entre otras cosas, vamos a definir determinadas variables de entorno necesarias para el correcto funcionamiento de dichos binarios, entre las que se encuentra el $ORACLE_HOME, que será aquella ruta en la que se encuentran ubicados los binarios en cuestión. Además, anexaremos el valor de dicha variable a $LD_LIBRARY_PATH y $PATH, haciendo para ello uso de los comandos: postgres@servidor2:~$ export ORACLE_HOME=/home/postgres/instantclient_21_1 postgres@servidor2:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME postgres@servidor2:~$ export PATH=$PATH:$ORACLE_HOME Al haber añadido la nueva ruta a la variable $PATH, ya será posible encontrar dicho binario sin indicar su ruta completa, así que vamos a comprobarlo haciendo uso de which, que nos devolverá la ruta en la que se ubica un binario en cuestión, por ejemplo sqlplus, ejecutando para ello el comando: postgres@servidor2:/home/postgres$ which sqlplus /home/postgres/instantclient_21_1/sqlplus Como era de esperar, gracias a haber añadido la nueva ruta a dicha variable de entorno, el binario es ahora totalmente accesible. Para verificar el correcto funcionamiento del mismo, vamos a llevar a cabo una conexión remota real a la base de datos, como si de una situación normal y corriente se tratase, haciendo para ello uso del comando: postgres@servidor2:/home/postgres$ sqlplus c##alvaro1/alvaro1@192.168.1.150/ORCLCDB SQL*Plus: Release 21.0.0.0.0 - Production on Thu Feb 4 11:34:53 2021 Version 21.1.0.0.0 Copyright (c) 1982, 2020, Oracle. All rights reserved. Hora de Ultima Conexion Correcta: Jue Feb 04 2021 11:12:43 -05:00 Conectado a: Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.3.0.0.0 Tal y como se puede apreciar en la salida del comando, nos hemos logrado conectar correctamente a la base de datos ubicada en la primera máquina servidora, por lo que vamos a proceder a listar todas las tablas que el usuario c##alvaro1 tiene privilegios para ver, es decir, aquellas que hemos creado manualmente. Para ello, ejecutaremos la instrucción: SQL&gt; SELECT * FROM cat; TABLE_NAME -------------------------------------------------------------------------------- TABLE_TYPE ----------- EMPLEADOS TABLE Efectivamente, la tabla que anteriormente hemos creado de forma manual es visible y accesible de forma remota desde una máquina PostgreSQL que actualmente actúa como cliente, de manera que podremos salir de dicho cliente ejecutando la instrucción exit para así continuar con la configuración del enlace. Tras comprobar que los binarios del cliente Oracle funcionan correctamente, es hora de llevar a cabo la compilación de oracle_fdw, de manera que tendremos que clonar el repositorio GitHub en el que está contenido el código fuente necesario para ello, haciendo por tanto uso del comando: postgres@servidor2:/home/postgres$ git clone https://github.com/laurenz/oracle_fdw.git Cloning into 'oracle_fdw'... remote: Enumerating objects: 77, done. remote: Counting objects: 100% (77/77), done. remote: Compressing objects: 100% (54/54), done. remote: Total 2223 (delta 39), reused 56 (delta 23), pack-reused 2146 Receiving objects: 100% (2223/2223), 1.35 MiB | 2.58 MiB/s, done. Resolving deltas: 100% (1513/1513), done. Una vez finalizada la clonación, vamos a listar una vez más el contenido del directorio actual para así verificar que dicha clonación ha sido efectiva: postgres@servidor2:/home/postgres$ ls -l total 8 drwxr-xr-x 4 postgres postgres 4096 Feb 4 11:28 instantclient_21_1 drwxr-xr-x 6 postgres postgres 4096 Feb 4 11:39 oracle_fdw Efectivamente, el repositorio de nombre oracle_fdw ha sido correctamente clonado en nuestra máquina local y podremos empezar a hacer uso del mismo, de manera que nos moveremos dentro dicho directorio para visualizar su contenido, ejecutando para ello el comando: postgres@servidor2:/home/postgres$ cd oracle_fdw/ Tras ello, listaremos el contenido existente una vez más para verificar que el repositorio se ha clonado correctamente: postgres@servidor2:/home/postgres/oracle_fdw$ ls -l total 460 -rw-r--r-- 1 postgres postgres 20254 Feb 4 11:39 CHANGELOG drwxr-xr-x 2 postgres postgres 4096 Feb 4 11:39 expected -rw-r--r-- 1 postgres postgres 1086 Feb 4 11:39 LICENSE -rw-r--r-- 1 postgres postgres 2932 Feb 4 11:39 Makefile drwxr-xr-x 2 postgres postgres 4096 Feb 4 11:39 msvc -rw-r--r-- 1 postgres postgres 231 Feb 4 11:39 oracle_fdw--1.0--1.1.sql -rw-r--r-- 1 postgres postgres 240 Feb 4 11:39 oracle_fdw--1.1--1.2.sql -rw-r--r-- 1 postgres postgres 1244 Feb 4 11:39 oracle_fdw--1.2.sql -rw-r--r-- 1 postgres postgres 207606 Feb 4 11:39 oracle_fdw.c -rw-r--r-- 1 postgres postgres 133 Feb 4 11:39 oracle_fdw.control -rw-r--r-- 1 postgres postgres 8667 Feb 4 11:39 oracle_fdw.h -rw-r--r-- 1 postgres postgres 44511 Feb 4 11:39 oracle_gis.c -rw-r--r-- 1 postgres postgres 98942 Feb 4 11:39 oracle_utils.c lrwxrwxrwx 1 postgres postgres 17 Feb 4 11:39 README.md -&gt; README.oracle_fdw -rw-r--r-- 1 postgres postgres 39227 Feb 4 11:39 README.oracle_fdw drwxr-xr-x 2 postgres postgres 4096 Feb 4 11:39 sql -rw-r--r-- 1 postgres postgres 313 Feb 4 11:39 TODO Efectivamente, todos los componentes necesarios para la compilación se encuentran ubicados dentro del directorio en cuestión. Explicar con detenimiento cómo funciona una compilación sería bastante extenso, por lo que aquí se puede encontrar otro artículo de mi blog en el que se explica y muestra un ejemplo práctico, aunque no es un requisito conocer cómo funciona para hacerlo. En resumen, cuando queremos compilar programas “grandes”, en los que hay gran cantidad de ficheros fuente (.c), headers (.h) y librerías, sería totalmente inviable compilarlos todos a mano para posteriormente linkar todos los .o en un único ejecutable. Es por ello que recurrimos al comando make, que nos permite hacerlo de una manera mucho más eficiente e inteligente gracias a un fichero de nombre Makefile, en el que se define mediante instrucciones, el orden en el que se compilarán los ficheros .c y linkarán los ficheros .o. Por tanto, para llevar a cabo dicha compilación e instalar el resultado en los correspondientes directorios de la máquina, ejecutaremos los comandos: postgres@servidor2:/home/postgres/oracle_fdw$ make gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -I/home/postgres/instantclient_21_1/sdk/include -I/home/postgres/instantclient_21_1/oci/include -I/home/postgres/instantclient_21_1/rdbms/public -I/home/postgres/instantclient_21_1 -I/usr/include/oracle/19.9/client -I/usr/include/oracle/19.9/client64 -I/usr/include/oracle/19.8/client -I/usr/include/oracle/19.8/client64 -I/usr/include/oracle/19.6/client -I/usr/include/oracle/19.6/client64 -I/usr/include/oracle/19.3/client -I/usr/include/oracle/19.3/client64 -I/usr/include/oracle/18.5/client -I/usr/include/oracle/18.5/client64 -I/usr/include/oracle/18.3/client -I/usr/include/oracle/18.3/client64 -I/usr/include/oracle/12.2/client -I/usr/include/oracle/12.2/client64 -I/usr/include/oracle/12.1/client -I/usr/include/oracle/12.1/client64 -I/usr/include/oracle/11.2/client -I/usr/include/oracle/11.2/client64 -I/usr/include/oracle/11.1/client -I/usr/include/oracle/11.1/client64 -I/usr/include/oracle/10.2.0.5/client -I/usr/include/oracle/10.2.0.5/client64 -I/usr/include/oracle/10.2.0.4/client -I/usr/include/oracle/10.2.0.4/client64 -I/usr/include/oracle/10.2.0.3/client -I/usr/include/oracle/10.2.0.3/client64 -I. -I./ -I/usr/include/postgresql/11/server -I/usr/include/postgresql/internal -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 -I/usr/include/mit-krb5 -c -o oracle_fdw.o oracle_fdw.c gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -I/home/postgres/instantclient_21_1/sdk/include -I/home/postgres/instantclient_21_1/oci/include -I/home/postgres/instantclient_21_1/rdbms/public -I/home/postgres/instantclient_21_1 -I/usr/include/oracle/19.9/client -I/usr/include/oracle/19.9/client64 -I/usr/include/oracle/19.8/client -I/usr/include/oracle/19.8/client64 -I/usr/include/oracle/19.6/client -I/usr/include/oracle/19.6/client64 -I/usr/include/oracle/19.3/client -I/usr/include/oracle/19.3/client64 -I/usr/include/oracle/18.5/client -I/usr/include/oracle/18.5/client64 -I/usr/include/oracle/18.3/client -I/usr/include/oracle/18.3/client64 -I/usr/include/oracle/12.2/client -I/usr/include/oracle/12.2/client64 -I/usr/include/oracle/12.1/client -I/usr/include/oracle/12.1/client64 -I/usr/include/oracle/11.2/client -I/usr/include/oracle/11.2/client64 -I/usr/include/oracle/11.1/client -I/usr/include/oracle/11.1/client64 -I/usr/include/oracle/10.2.0.5/client -I/usr/include/oracle/10.2.0.5/client64 -I/usr/include/oracle/10.2.0.4/client -I/usr/include/oracle/10.2.0.4/client64 -I/usr/include/oracle/10.2.0.3/client -I/usr/include/oracle/10.2.0.3/client64 -I. -I./ -I/usr/include/postgresql/11/server -I/usr/include/postgresql/internal -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 -I/usr/include/mit-krb5 -c -o oracle_utils.o oracle_utils.c gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -I/home/postgres/instantclient_21_1/sdk/include -I/home/postgres/instantclient_21_1/oci/include -I/home/postgres/instantclient_21_1/rdbms/public -I/home/postgres/instantclient_21_1 -I/usr/include/oracle/19.9/client -I/usr/include/oracle/19.9/client64 -I/usr/include/oracle/19.8/client -I/usr/include/oracle/19.8/client64 -I/usr/include/oracle/19.6/client -I/usr/include/oracle/19.6/client64 -I/usr/include/oracle/19.3/client -I/usr/include/oracle/19.3/client64 -I/usr/include/oracle/18.5/client -I/usr/include/oracle/18.5/client64 -I/usr/include/oracle/18.3/client -I/usr/include/oracle/18.3/client64 -I/usr/include/oracle/12.2/client -I/usr/include/oracle/12.2/client64 -I/usr/include/oracle/12.1/client -I/usr/include/oracle/12.1/client64 -I/usr/include/oracle/11.2/client -I/usr/include/oracle/11.2/client64 -I/usr/include/oracle/11.1/client -I/usr/include/oracle/11.1/client64 -I/usr/include/oracle/10.2.0.5/client -I/usr/include/oracle/10.2.0.5/client64 -I/usr/include/oracle/10.2.0.4/client -I/usr/include/oracle/10.2.0.4/client64 -I/usr/include/oracle/10.2.0.3/client -I/usr/include/oracle/10.2.0.3/client64 -I. -I./ -I/usr/include/postgresql/11/server -I/usr/include/postgresql/internal -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 -I/usr/include/mit-krb5 -c -o oracle_gis.o oracle_gis.c gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -shared -o oracle_fdw.so oracle_fdw.o oracle_utils.o oracle_gis.o -L/usr/lib/x86_64-linux-gnu -Wl,-z,relro -Wl,-z,now -L/usr/lib/llvm-7/lib -L/usr/lib/x86_64-linux-gnu/mit-krb5 -Wl,--as-needed -L/home/postgres/instantclient_21_1 -L/home/postgres/instantclient_21_1/bin -L/home/postgres/instantclient_21_1/lib -L/home/postgres/instantclient_21_1/lib/amd64 -lclntsh -L/usr/lib/oracle/19.9/client/lib -L/usr/lib/oracle/19.9/client64/lib -L/usr/lib/oracle/19.8/client/lib -L/usr/lib/oracle/19.8/client64/lib -L/usr/lib/oracle/19.6/client/lib -L/usr/lib/oracle/19.6/client64/lib -L/usr/lib/oracle/19.3/client/lib -L/usr/lib/oracle/19.3/client64/lib -L/usr/lib/oracle/18.5/client/lib -L/usr/lib/oracle/18.5/client64/lib -L/usr/lib/oracle/18.3/client/lib -L/usr/lib/oracle/18.3/client64/lib -L/usr/lib/oracle/12.2/client/lib -L/usr/lib/oracle/12.2/client64/lib -L/usr/lib/oracle/12.1/client/lib -L/usr/lib/oracle/12.1/client64/lib -L/usr/lib/oracle/11.2/client/lib -L/usr/lib/oracle/11.2/client64/lib -L/usr/lib/oracle/11.1/client/lib -L/usr/lib/oracle/11.1/client64/lib -L/usr/lib/oracle/10.2.0.5/client/lib -L/usr/lib/oracle/10.2.0.5/client64/lib -L/usr/lib/oracle/10.2.0.4/client/lib -L/usr/lib/oracle/10.2.0.4/client64/lib -L/usr/lib/oracle/10.2.0.3/client/lib -L/usr/lib/oracle/10.2.0.3/client64/lib postgres@servidor2:/home/postgres/oracle_fdw$ sudo make install /bin/mkdir -p '/usr/lib/postgresql/11/lib' /bin/mkdir -p '/usr/share/postgresql/11/extension' /bin/mkdir -p '/usr/share/postgresql/11/extension' /bin/mkdir -p '/usr/share/doc/postgresql-doc-11/extension' /usr/bin/install -c -m 755 oracle_fdw.so '/usr/lib/postgresql/11/lib/oracle_fdw.so' /usr/bin/install -c -m 644 .//oracle_fdw.control '/usr/share/postgresql/11/extension/' /usr/bin/install -c -m 644 .//oracle_fdw--1.2.sql .//oracle_fdw--1.0--1.1.sql .//oracle_fdw--1.1--1.2.sql '/usr/share/postgresql/11/extension/' /usr/bin/install -c -m 644 .//README.oracle_fdw '/usr/share/doc/postgresql-doc-11/extension/' Una vez que el resultado de la compilación ha sido correctamente instalado en los directorios locales de PostgreSQL, ya podremos hacer uso de dicha extensión. Sin embargo, en mi caso, a pesar de tener correctamente definidas las variables de entorno, el gestor no era capaz de encontrar las librerías compartidas de Oracle, de manera que ha sido necesario indicar de forma explícita la ruta a las mismas en un fichero de nombre oracle.conf dentro de /etc/ld.so.conf.d/, haciendo para ello uso del comando: postgres@servidor2:/home/postgres/oracle_fdw$ echo '/home/postgres/instantclient_21_1' | sudo tee /etc/ld.so.conf.d/oracle.conf Por último, tendremos que generar los enlaces necesarios y cargar en memoria las nuevas librerías compartidas que hemos introducido, de manera que ejecutaremos el comando: postgres@servidor2:/home/postgres/oracle_fdw$ sudo ldconfig Toda la configuración necesaria ha finalizado, pues únicamente faltaría generar el correspondiente enlace y empezar a hacer uso del mismo. Para ello, abriremos una shell de psql haciendo uso de la base de datos prueba2 desde el usuario actual, pues es el que cuenta con los privilegios necesarios para crear dicho enlace, haciendo para ello uso del comando: postgres@servidor2:/home/postgres/oracle_fdw$ psql -d prueba2 psql (11.9 (Debian 11.9-0+deb10u1)) Type "help" for help. Es muy importante que la conexión se realice a la base de datos prueba2, ya que es donde el rol alvaro2 tiene privilegios, pues de lo contrario, no podría utilizar dicho enlace. La creación del mismo es muy sencilla, llevándose a cabo mediante la ejecución del comando: prueba2=# CREATE EXTENSION oracle_fdw; CREATE EXTENSION La extensión que hará la función de enlace ya ha sido creada, pero para verificarlo, vamos a listar todas las extensiones existentes, haciendo para ello uso de la instrucción: prueba2=# \dx List of installed extensions Name | Version | Schema | Description ------------+---------+------------+-------------------------------------------------------------- dblink | 1.2 | public | connect to other PostgreSQL databases from within a database oracle_fdw | 1.2 | public | foreign data wrapper for Oracle access plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (3 rows) Efectivamente, la extensión oracle_fdw ha sido correctamente generada, por lo que el siguiente paso consistirá en crear un nuevo esquema (schema) al que posteriormente importaremos las tablas de la base de datos Oracle remota. Para ello, utilizaremos la instrucción: prueba2=# CREATE SCHEMA oracle; CREATE SCHEMA Nuestro esquema de nombre oracle ya ha sido generado, sin embargo, se encuentra actualmente vacío. Para llevar a cabo la importación de dichas tablas, tendremos que definir un nuevo servidor remoto que utilice la extensión que acabamos de generar, especificando, como es lógico, la dirección IP y la base de datos a la que pretendemos acceder, de la siguiente forma: prueba2=# CREATE SERVER oracle FOREIGN DATA WRAPPER oracle_fdw OPTIONS (dbserver '//192.168.1.150/ORCLCDB'); CREATE SERVER Sin embargo, definir un servidor remoto no es suficiente para acceder al mismo, ya que necesitamos “mapear” nuestro usuario local a un usuario existente en dicho gestor remoto que cuente con los privilegios necesarios para visualizar las tablas. En este caso, vamos a “mapear” el usuario local alvaro2 al usuario remoto c##alvaro1, indicando a su vez las credenciales del mismo, ejecutando para ello la instrucción: prueba2=# CREATE USER MAPPING FOR alvaro2 SERVER oracle OPTIONS (user 'c##alvaro1', password 'alvaro1'); CREATE USER MAPPING Dado que estas configuraciones las hemos llevado a cabo como un usuario administrador de la base de datos, tendremos que otorgar los privilegios necesarios al usuario alvaro2 para utilizar tanto el nuevo esquema generado como el servidor remoto definido, haciendo para ello uso de las instrucciones: prueba2=# GRANT ALL PRIVILEGES ON SCHEMA oracle TO alvaro2; GRANT prueba2=# GRANT ALL PRIVILEGES ON FOREIGN SERVER oracle TO alvaro2; GRANT La configuración como administrador ha finalizado, de manera que podremos salir del cliente ejecutando la instrucción exit para así realizar una nueva conexión a la base de datos prueba2 pero haciendo uso esta vez del rol alvaro2, y así verificar que puede utilizar dicho enlace, ejecutando para ello el comando: postgres@servidor1:~$ psql -h localhost -U alvaro2 -d prueba2 Password for user alvaro2: psql (11.9 (Debian 11.9-0+deb10u1)) SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) Type "help" for help. Para verificar que el enlace funciona correctamente, vamos a proceder a importar las tablas existentes en el esquema remoto c##alvaro1 a nuestro nuevo esquema local de nombre oracle, haciendo para ello uso del comando: prueba2=# IMPORT FOREIGN SCHEMA "C##ALVARO1" FROM SERVER oracle INTO oracle; IMPORT FOREIGN SCHEMA Listo, según la salida que dicha instrucción nos ha devuelto, el esquema ha sido correctamente importado, de manera que ya podremos hacer uso de las tablas existentes en nuestro esquema public por defecto y del nuevo que hemos generado, de nombre oracle, que contiene las tablas del gestor remoto Oracle ubicado en el servidor oracle1. Es importante mencionar que en caso de ejecutar la instrucción \d, las tablas remotas no se mostrarían, ya que por defecto, PostgreSQL busca en el esquema por defecto public. Si quisiésemos cambiar la ruta de búsqueda al nuevo esquema generado, tendríamos que ejecutar el comando SET search_path='oracle';, aunque no es necesario hacerlo para consultar dichas tablas. La intención es mostrar los datos de los Empleados (tabla que se encuentra en el esquema public) junto al nombre del departamento al que pertenecen, información que podremos obtener de Departamentos (tabla que se encuentra en el esquema oracle), utilizando para ello la clave que tienen en común ambas tablas. La consulta a ejecutar sería: prueba2=# SELECT Empleados.DNI AS DNI, Empleados.Nombre AS Nombre, Empleados.Direccion AS Direccion, Empleados.Telefono AS Telefono, Empleados.FechaNacimiento AS FechaNacimiento, Empleados.Salario AS Salario, Departamentos.Nombre AS Departamento prueba2-# FROM oracle.Empleados Empleados, Departamentos prueba2-# WHERE Empleados.Departamento = Departamentos.Identificador; dni | nombre | direccion | telefono | fechanacimiento | salario | departamento -----------+---------------------------+------------------------+-----------+---------------------+---------+------------------ 90389058R | Joaquin Marrero Covas | C/ Hijuela de Lojo, 22 | 618385118 | 1997-01-28 00:00:00 | 1446 | Administración 18232747A | Aristarco Caban Meraz | Puerta Nueva, 67 | 691204722 | 1994-05-07 00:00:00 | 4789 | Seguridad 94106513N | Marian Fonseca Betancourt | C/ Manuel Iradier, 37 | 638415823 | 1979-07-17 00:00:00 | 2561 | Seguridad 12777631G | Merlino Rosado Cordero | C/ Henan Cortes, 58 | 609841755 | 1993-11-27 00:00:00 | 7961 | Recursos Humanos 68219319P | Tabare Chapa Alcantar | C/ Arana, 12 | 682227206 | 1992-03-09 00:00:00 | 8568 | Informática 67227129S | Ian Esquivel Laboy | C/ Inglaterra, 64 | 728005136 | 1992-07-21 00:00:00 | 3519 | Administración 52315160G | Heinz Collado Caraballo | Escuadro, 60 | 600173822 | 1984-02-13 00:00:00 | 1672 | Recursos Humanos 85145590G | Anabel Lerma Dominguez | Crta. Cadiz, 1 | 675014823 | 1981-01-07 00:00:00 | 5919 | Administración 56228957Y | Dinorah Viera Tello | Ctra. Villena, 22 | 642852778 | 1987-05-28 00:00:00 | 2567 | Seguridad 61242562W | Manases Castillo Camacho | Ctra. Hornos, 91 | 607853354 | 1991-10-29 00:00:00 | 4270 | Informática (10 rows) Donde: SELECT: Indicamos las columnas que queremos mostrar de la información obtenida, de la forma [tabla].[columna]. FROM: Hacemos un JOIN de la tabla Empleados que se encuentra en el esquema public junto con la tabla Departamentos que se encuentra en el esquema oracle, de la forma [esquema].[tabla], para así poder mostrar información de ambas tablas en la misma consulta. WHERE: Establecemos la condición del JOIN, que deberá ser aquella columna mediante la cual vamos a unir los registros devueltos. Como es lógico, será el código del departamento, pues es la columna que se repite en ambas tablas. Como era de esperar, la consulta ha vuelto a realizarse sin ningún problema y ha devuelto la información que debería. Otra utilidad que podemos aprovechar es la capacidad de copiar las tablas de un esquema a otro, utilizando el resultado de una consulta simple para crear una tabla a partir de la misma. Por ejemplo, podríamos copiar la tabla Empleados desde el esquema oracle haciendo uso de la siguiente instrucción: prueba2=# CREATE TABLE Empleados prueba2-# AS (SELECT * prueba2(# FROM oracle.Empleados); SELECT 10 En dicha instrucción, hemos realizado una consulta a la tabla Empleados ubicada en el esquema oracle generado a partir de la base de datos existente en el servidor oracle1, utilizando la respuesta obtenida para crear una nueva tabla con el mismo nombre, que se almacenará ahora en el esquema public, y que podremos empezar a utilizar sin necesidad de recurrir al enlace con el primer servidor. Si consultamos la nueva tabla generada, obtendremos el siguiente resultado: prueba2=# SELECT * prueba2-# FROM Empleados; dni | nombre | direccion | telefono | fechanacimiento | salario | departamento -----------+---------------------------+------------------------+-----------+---------------------+---------+-------------- 90389058R | Joaquin Marrero Covas | C/ Hijuela de Lojo, 22 | 618385118 | 1997-01-28 00:00:00 | 1446 | 10 18232747A | Aristarco Caban Meraz | Puerta Nueva, 67 | 691204722 | 1994-05-07 00:00:00 | 4789 | 30 94106513N | Marian Fonseca Betancourt | C/ Manuel Iradier, 37 | 638415823 | 1979-07-17 00:00:00 | 2561 | 30 12777631G | Merlino Rosado Cordero | C/ Henan Cortes, 58 | 609841755 | 1993-11-27 00:00:00 | 7961 | 20 68219319P | Tabare Chapa Alcantar | C/ Arana, 12 | 682227206 | 1992-03-09 00:00:00 | 8568 | 40 67227129S | Ian Esquivel Laboy | C/ Inglaterra, 64 | 728005136 | 1992-07-21 00:00:00 | 3519 | 10 52315160G | Heinz Collado Caraballo | Escuadro, 60 | 600173822 | 1984-02-13 00:00:00 | 1672 | 20 85145590G | Anabel Lerma Dominguez | Crta. Cadiz, 1 | 675014823 | 1981-01-07 00:00:00 | 5919 | 10 56228957Y | Dinorah Viera Tello | Ctra. Villena, 22 | 642852778 | 1987-05-28 00:00:00 | 2567 | 30 61242562W | Manases Castillo Camacho | Ctra. Hornos, 91 | 607853354 | 1991-10-29 00:00:00 | 4270 | 40 (10 rows) Como se puede apreciar, el contenido es exactamente el mismo que el existente en la tabla ubicada en el gestor remoto, por lo que podemos concluir que su clonación ha sido efectiva y que los servidores tienen conectividad entre sí mediante los enlaces creados, sea cual sea el sentido utilizado.]]></summary></entry></feed>