<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>John Ruiz Campos</title>
	<atom:link href="https://johnruizcampos.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://johnruizcampos.com</link>
	<description>DevOps &#38; Cloud</description>
	<lastBuildDate>Sat, 01 Feb 2025 19:58:58 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2.6</generator>

<image>
	<url>https://johnruizcampos.com/wp-content/uploads/cropped-johnruiz_logo-32x32.png</url>
	<title>John Ruiz Campos</title>
	<link>https://johnruizcampos.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Cómo firmar imágenes Docker utilizando Docker Content Trust</title>
		<link>https://johnruizcampos.com/como-firmar-imagenes-docker/</link>
		
		<dc:creator><![CDATA[John Ruiz]]></dc:creator>
		<pubDate>Tue, 02 May 2023 13:35:31 +0000</pubDate>
				<category><![CDATA[Sin categoría]]></category>
		<guid isPermaLink="false">https://johnruizcampos.com/?p=378</guid>

					<description><![CDATA[Hace poco, tuvimos el requerimiento de un cliente para realizar el firmado de sus imágenes Docker almacenadas en Azure Container Registry (ACR). Dado que era una tarea relativamente nueva para nosotros, tuvimos que documentarnos en&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Hace poco, tuvimos el requerimiento de un cliente para realizar el firmado de sus imágenes Docker almacenadas en Azure Container Registry (ACR). Dado que era una tarea relativamente nueva para nosotros, tuvimos que documentarnos en el tema y realizar varias pruebas de concepto.</p>



<p>Luego de algunos intentos y de resolver varios problemas que se nos presentaron en el camino, logramos definir un proceso claro y repetible para el firmado de las imágenes Docker, a la vez que lo implementamos exitosamente en el cliente.</p>



<p>Debido a la dificultad que presentó realizar esta tarea y a la falta de consistencia en la documentación encontrada decidí escribir este artículo, en el cual trato de integrar todo el conocimiento y herramientas necesarias para realizar el correcto firmado de imágenes Docker siguiendo el protocolo Docker Content Trust (DCT).</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f530.png" alt="🔰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Requerimientos Iniciales</h2>



<ul>
<li>El ordenador desde el cual se llevará a cabo el presente tutorial, debe tener instaladas y configuradas las siguientes herramientas:
<ul>
<li>Docker Engine (servidor): <a href="https://docs.docker.com/engine/install/" target="_blank" rel="noreferrer noopener nofollow"><strong>Guía de instalación</strong></a></li>



<li>Notary CLI (cliente): <strong><a href="https://github.com/notaryproject/notary/releases" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a></strong></li>



<li>Azure CLI (cliente): <strong><a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli" data-type="URL" data-id="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a></strong></li>
</ul>
</li>
</ul>



<ul>
<li></li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f309.png" alt="🌉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> La Arquitectura de Docker Content Trust (DCT)</h2>



<p>Los componentes necesarios para realizar el proceso de firmado de imágenes Docker bajo el esquema DCT son los siguientes:</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f511.png" alt="🔑" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Las Keys para el firmado</h3>



<p>Para el firmado de imágenes Docker necesitaremos principalmente 3 tipos de Keys (claves). Las describimos a continuación:</p>



<ul>
<li><strong>Root Key:</strong> Es la clave raíz para todo un registro de contenedores (container registry) y es la clave base sobre la cual se crean las Repository keys. En caso de perder la Root Key, es <strong><em>imposible</em> </strong>recuperarla.</li>



<li><strong>Repository Key:</strong> Es una clave correspondiente a un repositorio de imágenes en particular y servirá para firmar <em><strong>sólo los tags pertenecientes</strong></em> a dicho repositorio. Es creada a partir de la Root Key por lo que de perderse ésta ya no podrán crearse nuevas Repository Keys.</li>



<li><strong>Delegation Key:</strong> Es la key correspondiente a un usuario con permisos para firmar las imágenes de un repositorio particular. Estos permisos se asignan mediante un proceso llamado <em><strong>delegación</strong></em>.</li>
</ul>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="632" height="494" src="https://johnruizcampos.com/wp-content/uploads/key_types_sign_docker_images.jpg" alt="Different Key Types to sign Docker images" class="wp-image-407" srcset="https://johnruizcampos.com/wp-content/uploads/key_types_sign_docker_images.jpg 632w, https://johnruizcampos.com/wp-content/uploads/key_types_sign_docker_images-300x234.jpg 300w" sizes="(max-width: 632px) 100vw, 632px" title="key types sign docker images"><figcaption class="wp-element-caption">Different Key Types to sign Docker images</figcaption></figure></div>


<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f6e1.png" alt="🛡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> El Notary Server</h3>



<p>Cuando se generan las firmas de las imágenes Docker, éstas no se guardan con la imagen Docker en sí sino que se almacenan en un servidor Llamado <strong><em>Notary Server</em></strong>. Hay que tener en cuentan que <em><strong>no todos</strong></em> los registros de contenedores de imágenes soportan DCT y por ende cuentan con un Notary Server habilitado.</p>



<p>En nuestro caso usaremos <strong><a href="https://azure.microsoft.com/en-us/products/container-registry" target="_blank" rel="noreferrer noopener nofollow">Azure Container Registry (ACR)</a></strong> para almacenar nuestras imágenes el cual nos permite habilitar el protocolo DCT en sus registros de contenedores. En este caso, el Notary Server es accesible por la misma URL del registro de contenedor (usando https) como en la imagen de ejemplo.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="986" height="398" src="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_dct_notary_server.jpg" alt="Azure Container Registry ACR, Notary Server and DCT" class="wp-image-406" srcset="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_dct_notary_server.jpg 986w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_dct_notary_server-300x121.jpg 300w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_dct_notary_server-768x310.jpg 768w" sizes="(max-width: 986px) 100vw, 986px" title="azure container registry dct notary server"><figcaption class="wp-element-caption">Azure Container Registry and Notary Server</figcaption></figure></div>


<p>Cuando activamos el Notary Server, éste se habilita para el registro de contenedores, es decir para todos los repositorios almacenados en dicho registro. Adicionalmente, un repositorio de imágenes puede tener <strong><em>tags</em></strong> firmados y no firmados conviviendo sin problemas.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Desplegando un registro de contenedores en Azure (ACR)</h2>



<p>Antes de todo debemos tener desplegado un registro de contenedores Docker, en nuestro caso haremos uso de Azure Container Registry (ACR) puesto que incluye el soporte de Docker Content Trust para el firmado de imágenes.</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f433.png" alt="🐳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Creando el registro</h3>



<p>Al momento de crear el registro de contenedores, le damos un nombre y seleccionamos como <em><strong>SKU</strong></em> a <strong><em>Premium</em></strong> puesto que este Tier es el que nos permitirá habilitar el servicio de Docker Content Trust: </p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="762" height="583" src="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_1.jpg" alt="Creating an Azure Container Registry" class="wp-image-455" srcset="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_1.jpg 762w, https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_1-300x230.jpg 300w" sizes="(max-width: 762px) 100vw, 762px" title="azure create container registry 1"><figcaption class="wp-element-caption">Creating an Azure Container Registry</figcaption></figure></div>


<p>En la siguiente página habilitamos el acceso público al registro, dejamos las demás opciones por defecto y creamos el registro:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="442" height="666" src="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_2.jpg" alt="Creating an Azure Container Registry" class="wp-image-456" srcset="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_2.jpg 442w, https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_2-199x300.jpg 199w" sizes="(max-width: 442px) 100vw, 442px" title="azure create container registry 2"><figcaption class="wp-element-caption">Creating an Azure Container Registry</figcaption></figure></div>


<p>Una vez creado el registro debemos habilitar DCT, para ello en la lista de opciones del registro (panel izquierdo) vamos a <strong>Policies</strong> <strong>➜</strong> <strong>Content trust</strong>, lo habilitamos seleccionando <strong>Status</strong> <strong>➜</strong> <strong>Enabled</strong> y guardamos (Save):</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="751" height="517" src="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_3.jpg" alt="Enabling Docker Content Trust in ACR" class="wp-image-457" srcset="https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_3.jpg 751w, https://johnruizcampos.com/wp-content/uploads/azure_create_container_registry_3-300x207.jpg 300w" sizes="(max-width: 751px) 100vw, 751px" title="azure create container registry 3"><figcaption class="wp-element-caption">Enabling Docker Content Trust in ACR</figcaption></figure></div>


<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f465.png" alt="👥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Asignando permisos para el firmado de imágenes</h3>



<p>Adicionalmente debemos asignar permisos (rol) a nuestro usuario para que éste tenga la capacidad de firmar las imágenes almacenadas en nuestro registro.</p>



<p>En el panel izquierdo vamos a <strong><em>Access control (IAM)</em></strong> y en la parte inferior del panel principal, en la sección <strong><em>Grant access to this resource</em></strong> seleccionamos la opción <strong><em>Add role assignment:</em></strong></p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="621" height="566" src="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_1.jpg" alt="Adding AcrImageSigner role to User" class="wp-image-464" srcset="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_1.jpg 621w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_1-300x273.jpg 300w" sizes="(max-width: 621px) 100vw, 621px" title="azure container registry add iam role 1"><figcaption class="wp-element-caption">Accessing to Access control (IAM) in ACR</figcaption></figure></div>


<p>En la lista de roles que se mostrarán buscamos el rol <strong><em>AcrImageSigner</em></strong>, lo seleccionamos y hacemos click en <strong><em>Next</em></strong>:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="626" height="553" src="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_2.jpg" alt="Adding AcrImageSigner role to User: Selecting Role" class="wp-image-465" srcset="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_2.jpg 626w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_2-300x265.jpg 300w" sizes="(max-width: 626px) 100vw, 626px" title="azure container registry add iam role 2"><figcaption class="wp-element-caption">Adding AcrImageSigner role to User: Selecting Role</figcaption></figure></div>


<p>En la página siguiente seleccionamos <strong><em>+Select members</em></strong> y se nos mostrará a la derecha un panel en el cual buscaremos a nuestro usuario ingresando el <strong><em>email</em></strong>. Seleccionamos a nuestro usuario y ya aparecerá en la sección <strong><em>Members</em></strong> del panel principal. Seleccionamos <strong><em>Review + assign</em></strong> guardando los cambios:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="715" height="531" src="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_3.jpg" alt="Adding AcrImageSigner role to User: Applying" class="wp-image-466" srcset="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_3.jpg 715w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_3-300x223.jpg 300w" sizes="(max-width: 715px) 100vw, 715px" title="azure container registry add iam role 3"><figcaption class="wp-element-caption">Adding AcrImageSigner role to User: Applying</figcaption></figure></div>


<p>Verificamos los permisos asignados regresando a la página inicial de <strong><em>Access control (IAM)</em></strong>, seleccionamos la opción <strong><em>View my access</em></strong> y se nos mostrará un panel derecho con la lista de roles asignados a nuestro usuario. Revisamos que el rol <strong><em>AcrImageSigner</em></strong> se encuentre en la lista:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="945" height="377" src="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_4.jpg" alt="Adding AcrImageSigner role to User: Verifying" class="wp-image-467" srcset="https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_4.jpg 945w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_4-300x120.jpg 300w, https://johnruizcampos.com/wp-content/uploads/azure_container_registry_add_iam_role_4-768x306.jpg 768w" sizes="(max-width: 945px) 100vw, 945px" title="azure container registry add iam role 4"><figcaption class="wp-element-caption">Adding AcrImageSigner role to User: Verifying</figcaption></figure></div>


<p>Con esto ya contamos con un registro de contenedores con DCT habilitado y con los permisos adecuados para realizar el firmado de imágenes.</p>



<p>Para el caso de los registros de contenedores que no soporten DCT, la solución viene por desplegar un Notary Server <em><strong>de manera independiente</strong></em>. El procedimiento se sale del alcance del presente artículo, pero usted puede guiarse del proyecto oficial de Notary en <strong><a href="https://github.com/notaryproject/notary/blob/master/docs/running_a_service.md" target="_blank" rel="noreferrer noopener nofollow">GitHub</a></strong>.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2712.png" alt="✒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> El Proceso de Firmado</h2>



<p>Antes de todo debemos iniciar sesión en Azure y el registro de contenedores sobre el cual trabajaremos:</p>



<pre class="wp-block-code has-regular-font-size"><code>az login
az acr login --name johnrc.azurecr.io</code></pre>



<p>Verificamos que estamos correctamente conectados ejecutando:</p>



<pre class="wp-block-code" style="font-size:15px"><code><strong># az acr list -o table</strong>
NAME   RESOURCE  LOCATION SKU     LOGIN             CREATION             ADMIN
       GROUP                      SERVER            DATE                 ENABLED
------ --------- -------- ------- ----------------- -------------------- -------
johnrc rg_johnrc eastus   Premium johnrc.azurecr.io 2023-05-01T01:27:43Z False</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f5dd.png" alt="🗝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Generando las Delegation Keys</h3>



<p>Lo primero que tenemos que realizar es generar una Delegation Key para la firma de las imágenes. Para ello, ejecutamos:</p>



<pre class="wp-block-code has-regular-font-size"><code>docker trust key generate john</code></pre>



<p>Se nos pedirá un password para encriptar la nueva Delegation Key generada. El comando creará un par de archivos, una clave pública y una clave privada. El archivo de clave privada se almacena siempre en la carpeta <strong><em>~/.docker/trust/private</em></strong>, y el archivo de clave pública en la carpeta desde la cual ejecutamos el comando:</p>



<pre class="wp-block-code has-regular-font-size"><code>~/files # docker trust key generate john
Generating key for john...
Enter passphrase for new john key with ID 6d6ade9:
Repeat passphrase for new john key with ID 6d6ade9:
Successfully generated and loaded private key. Corresponding public key available: /root/files/john.pub</code></pre>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="810" height="442" src="https://johnruizcampos.com/wp-content/uploads/generating_delegation_key_dct.jpg" alt="Generating Delegation Key" class="wp-image-415" srcset="https://johnruizcampos.com/wp-content/uploads/generating_delegation_key_dct.jpg 810w, https://johnruizcampos.com/wp-content/uploads/generating_delegation_key_dct-300x164.jpg 300w, https://johnruizcampos.com/wp-content/uploads/generating_delegation_key_dct-768x419.jpg 768w" sizes="(max-width: 810px) 100vw, 810px" title="generating delegation key dct"><figcaption class="wp-element-caption">Generating a Delegation Key using DCT</figcaption></figure></div>


<p>Podemos verificar la creación de la delegation key con el cliente de Notary, ejecutamos:</p>



<pre class="wp-block-code"><code>notary key list</code></pre>



<pre class="wp-block-code has-regular-font-size"><code># notary key list

ROLE    GUN    KEY ID           LOCATION
----    ---    -------------    --------
john           6d6ade9e896df    /root/.docker/trust/private</code></pre>



<p>En caso de que ya contemos con un par de archivos de clave pública y privada que queramos reutilizar (creadas anteriormente con OpenSSL por ejemplo), podemos cargar la clave privada con el siguiente comando:</p>



<pre class="wp-block-code"><code>docker trust key load private.key --name john</code></pre>



<pre class="wp-block-code"><code>~/files # docker trust key load private.key --name john
Loading key from "private.key"...
Enter passphrase for new john key with ID 778705c:
Repeat passphrase for new john key with ID 778705c:
Successfully imported key from private.key</code></pre>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="844" height="502" src="https://johnruizcampos.com/wp-content/uploads/delegation_loading_private_key_from_file_dct.jpg" alt="Loading a Delegation Private Key from file" class="wp-image-414" srcset="https://johnruizcampos.com/wp-content/uploads/delegation_loading_private_key_from_file_dct.jpg 844w, https://johnruizcampos.com/wp-content/uploads/delegation_loading_private_key_from_file_dct-300x178.jpg 300w, https://johnruizcampos.com/wp-content/uploads/delegation_loading_private_key_from_file_dct-768x457.jpg 768w" sizes="(max-width: 844px) 100vw, 844px" title="delegation loading private key from file dct"><figcaption class="wp-element-caption">Loading a Delegation Private Key from file</figcaption></figure></div>


<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Creando las reglas de Delegación</h3>



<p>Una regla de delegación permite indicar quien puede firmar imágenes en un repositorio específico. Para ello necesitamos el nombre del usuario, el archivo de clave pública generado en el paso anterior y el repositorio sobre el cual se asignarán los permisos.</p>



<p>El archivo de clave pública puede estar en distintos formatos (.pub/.crt/.pem), esto depende de si generamos la <em><strong>delegation key</strong></em> con <strong><em>docker trust</em></strong>, o de si la creamos manualmente con una utilidad como OpenSSL.</p>



<pre class="wp-block-code" style="font-size:16px"><code><strong>docker trust signer add --key <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">john.pub</mark> <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">john</mark> <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">johnrc.azurecr.io/java11-utils</mark></strong></code></pre>



<p>En este ejemplo el nombre del usuario es <strong><em>john</em></strong>, el archivo de clave pública es <em><strong>john.pub</strong></em> y el repositorio <strong><em>johnrc.azurecr.io/java11-utils</em></strong>.</p>



<pre class="wp-block-code" style="font-size:15px"><code> <strong># docker trust signer add --key john.pub john johnrc.azurecr.io/java11-utils
Adding signer "john" to johnrc.azurecr.io/java11-utils...
Initializing signed repository for johnrc.azurecr.io/java11-utils...
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID 86b2ff1:
Repeat passphrase for new root key with ID 86b2ff1:
Enter passphrase for new repository key with ID a2dbb4a:
Repeat passphrase for new repository key with ID a2dbb4a:
Successfully initialized "johnrc.azurecr.io/java11-utils"
Successfully added signer: john to johnrc.azurecr.io/java11-utils</strong></code></pre>



<p>Dado que es la primera vez que creamos una delegación en el repositorio <strong><em>johnrc.azurecr.io/java11-utils</em></strong>, DCT procede a inicializarlo y para ello se crean la <em>Root Key</em> y <em>Repository Key</em> correspondientes. Se nos pedirán passwords para encriptar la <em>Root Key</em> y <em>Repository Key</em>. Se recomienda usar passwords complejos y diferentes así como guardarlos en un lugar seguro para su uso posterior.</p>



<p>Verificamos las claves creadas. Podemos ver las claves raíz (root), de usuario (john) y de repositorio (targets): </p>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>notary key list</strong>
ROLE     GUN                        KEY ID               LOCATION
----     ---                        ------               --------
root                                18d9dffb748b55280ee  /root/.docker/trust/private
john                                42e9f806d309c9c4828  /root/.docker/trust/private
targets  ...zurecr.io/<strong>java11-utils</strong>  5a9647e8d848082f4d1  /root/.docker/trust/private</code></pre>



<p>La próxima vez que creemos una delegación sobre un repositorio diferente, por ejemplo <strong><em>johnrc.azurecr.io/python9-utils</em></strong>, sólo se creará una nueva <em>Repository Key</em> para dicho repositorio y se reutilizará la <em>Root Key</em> ya creada anteriormente. Vale recordar que la <em>Root Key</em> se crea sólo una vez y las diferentes <em>Repository Keys</em> se generan basadas en ella.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img decoding="async" width="1024" height="440" src="https://johnruizcampos.com/wp-content/uploads/creating_delegation_user_repository_dct-1024x440.jpg" alt="Creating a Delegation Rule using a public key file" class="wp-image-419" srcset="https://johnruizcampos.com/wp-content/uploads/creating_delegation_user_repository_dct-1024x440.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/creating_delegation_user_repository_dct-300x129.jpg 300w, https://johnruizcampos.com/wp-content/uploads/creating_delegation_user_repository_dct-768x330.jpg 768w, https://johnruizcampos.com/wp-content/uploads/creating_delegation_user_repository_dct.jpg 1214w" sizes="(max-width: 1024px) 100vw, 1024px" title="creating delegation user repository dct"><figcaption class="wp-element-caption">Creating a Delegation Rule using a public key file</figcaption></figure></div>


<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/270d.png" alt="✍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Firmando y subiendo una imagen Docker</h3>



<p>Para realizar nuestra prueba de firmado, crearemos una imagen Docker de ejemplo en el repositorio <strong>johnrc.azurecr.io/java11-utils</strong> colocándole el tag <strong><em>signed</em></strong>: </p>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>docker build -t johnrc.azurecr.io/java11-utils:signed .</strong>
&#91;+] Building 25.1s (6/6) FINISHED
 =&gt; &#91;internal] load build definition from Dockerfile_alpine_java11               0.0s
 =&gt; =&gt; transferring dockerfile: 269B                                             0.0s
 =&gt; &#91;internal] load .dockerignore                                                0.0s
 =&gt; =&gt; transferring context: 2B                                                  0.0s
 =&gt; &#91;internal] load metadata for docker.io/library/alpine:3.17.2                 1.2s
 =&gt; CACHED &#91;1/2] FROM docker.io/library/alpine:3.17.2@sha256:ff6bdca1701f3a      0.0s
 =&gt; &#91;2/2] RUN apk add git util-linux &amp;&amp; apk add openjdk11                       22.8s
 =&gt; exporting to image 1.1s
 =&gt; =&gt; exporting layers 1.1s
 =&gt; =&gt; writing image sha256:1baba7d5e04a91b85fb4e4347aeb03ba456e4b36ef6bae       0.0s
 =&gt; =&gt; naming to johnrc.azurecr.io/java11-utils:signed
#
<strong># docker images</strong>
<strong>REPOSITORY                       TAG       IMAGE ID       CREATED      SIZE</strong>
johnrc.azurecr.io/java11-utils   signed    1baba7d5e04a   3 hours ago   297MB</code></pre>



<p>Ya estamos listos para firmar y subir nuestra nueva imagen Docker al registro de contenedor de Azure. En el momento de subir la imagen, la información de firmado se guardará en el Notary Server. Ejecutamos:</p>



<pre class="wp-block-code" style="font-size:16px"><code><strong>docker push <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">--disable-content-trust=false</mark> <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">johnrc.azurecr.io/java11-utils:</mark><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">signed</mark></strong></code></pre>



<p>En este ejemplo habilitamos el firmado de la imagen con la opción <strong><em>&#8211;disable-content-trust=false</em></strong> e indicamos el repositorio y tag de la imagen a firmar como <strong><em>johnrc.azurecr.io/java11-utils:signed</em></strong></p>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>docker push --disable-content-trust=false johnrc.azurecr.io/java11-utils:signed</strong>
The push refers to repository &#91;johnrc.azurecr.io/java11-utils]
6cb6697bc6eb: Layer already exists
7cd52847ad77: Layer already exists
signed: digest: sha256:6ed4ea21547483944b71568e53d6f7664a6ea4b3c94d8a0a8640d1d030879b0c size: 741
Signing and pushing trust metadata
Enter passphrase for john key with ID 778705c:
Successfully signed johnrc.azurecr.io/java11-utils:signed</code></pre>



<p>En este ejemplo tenemos localmente 2 imágenes: la que acabamos de crear y subir firmada (tag <em>signed</em>) y otra ya existente (tag <em>v1</em>) que se subió sin la opción <strong>&#8211;disable-content-trust=false</strong> (sin firmar):</p>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>docker images</strong>
<strong>REPOSITORY                       TAG       IMAGE ID       CREATED       SIZE</strong>
johnrc.azurecr.io/java11-utils   <strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">signed</mark></strong>    1baba7d5e04a   3 hours ago   297MB
johnrc.azurecr.io/java11-utils   <strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">v1</mark></strong>        8d25badcd502   3 hours ago   14.4MB</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Verificando las firmas e identificando tags firmados</h3>



<p>El hecho de que le hayamos colocado el tag <em><strong>signed</strong> </em>a una imagen no quiere decir que la imagen ya esté firmada. Para verificar si una imagen está realmente firmada o no vamos a utilizar el siguiente comando:</p>



<pre class="wp-block-code" style="font-size:16px"><code><strong>docker trust inspect <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">--pretty</mark> <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">johnrc.azurecr.io/java11-utils:</mark><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">signed</mark></strong></code></pre>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>docker trust inspect --pretty johnrc.azurecr.io/java11-utils:signed</strong>

<strong>Signatures for johnrc.azurecr.io/java11-utils:signed</strong>

<strong>SIGNED TAG</strong>   DIGEST                                                         <strong>SIGNERS</strong>
<strong>signed</strong>       6ed4ea21547483944b71568e53d6f7664a6ea4b3c94d8a0a8640d1d03087   <strong>john</strong>

List of signers and their keys for johnrc.azurecr.io/java11-utils:signed

<strong>SIGNER    KEYS
john      778705c2e4f4</strong>

Administrative keys for johnrc.azurecr.io/java11-utils:signed

  <strong>Repository Key:  a62a0297649e16318b32853af85dd8bcbc7fb276da62fed6f5d875fbda8f</strong>
  <strong>Root Key:  4f719a5c838d9d22eccec72ed59c49935783fb2bb92ba289d5334bad14d5</strong></code></pre>



<p>Podemos ver que se indica quién firmó la imagen (<strong>john</strong>), el id de la clave de delegación utilizada (<strong>778705c2e4f4</strong>), y el Repository/Root Keys utilizados.</p>



<p>En caso analizemos el tag <strong><em>v1</em></strong> con el mismo comando, veremos que el resultado es diferente:</p>



<pre class="wp-block-code" style="font-size:15px"><code># <strong>docker trust inspect --pretty johnrc.azurecr.io/java11-utils:v1</strong>

<strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">No signatures for johnrc.azurecr.io/java11-utils:v1</mark></strong>

List of signers and their keys for johnrc.azurecr.io/java11-utils:v1

SIGNER    KEYS
john      42e9f806d309

Administrative keys for johnrc.azurecr.io/java11-utils:v1

  Repository Key:       5a9647e8d84808d7003b4416f29f18810da9dbbd55c62a90cbf93c02f4d1
  Root Key:     368988454a765d285cd9489aec92d0a676dc797f07d94f92096bc08d0b086</code></pre>



<p>El resultado indica claramente que la imagen analizada no cuenta con firma alguna.</p>



<p>Otra manera de verificar los tags firmados y no firmados en un repositorio Docker pero que funciona sólo para Azure Container Registry es ejecutando el siguiente comando de Azure CLI:</p>



<pre class="wp-block-code" style="font-size:15px"><code><strong><strong>az acr repository show-tags -n <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">johnrc.azurecr.io</mark> --repository <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">java11-utils</mark> --detail -o table</strong></strong></code></pre>



<p>Podemos ver en la columna <em><strong>Name</strong></em> los tags almacenados en el registro y en la columna <strong><em>Signed</em></strong> si dicho tag está firmado o no:</p>



<pre class="wp-block-code has-small-font-size"><code><strong># az acr repository show-tags -n johnrc.azurecr.io --repository java11-utils --detail -o table</strong>
<strong>CreatedTime    Digest                          LastUpdateTime       Name    Signed</strong>
-------------  ------------------------------  -------------------  ------  --------
2023-04-21T17  sha256:826dc034c5846fa3599bdec  2023-04-21T17:06:37  <strong>latest  False</strong>
2023-04-21T19  sha256:6ed4ea21547483944b71568  2023-04-21T19:25:53  <strong>signed  <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">True</mark></strong></code></pre>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Cómo instalar Jenkins en Kubernetes</title>
		<link>https://johnruizcampos.com/como-instalar-jenkins-en-kubernetes/</link>
		
		<dc:creator><![CDATA[John Ruiz]]></dc:creator>
		<pubDate>Mon, 16 Jan 2023 01:57:56 +0000</pubDate>
				<category><![CDATA[Sin categoría]]></category>
		<guid isPermaLink="false">https://johnruizcampos.com/?p=229</guid>

					<description><![CDATA[Hace poco a solicitud de un cliente tuve que realizar la instalación de Jenkins sobre un clúster de Kubernetes EKS (AWS), y de esta manera poder aprovechar las características de alta disponibilidad, escalamiento horizontal, inicio&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Hace poco a solicitud de un cliente tuve que realizar la instalación de Jenkins sobre un clúster de Kubernetes EKS (AWS), y de esta manera poder aprovechar las características de alta disponibilidad, escalamiento horizontal, inicio de agentes a demanda, etc. que brinda Kubernetes.</p>



<p>Luego de revisar la diversa documentación existente y de realizar varias pruebas de concepto, encontré que a parte del despliegue del clúster en sí, eran necesarios otros componentes que permitieran el correcto funcionamiento de una aplicación en K8s (Jenkins en este caso).</p>



<p>En el presente proyecto, he tratado de describir este proceso de la manera más detallada posible. Aunque puede parecer tedioso en un inicio, una vez comprendidos los conceptos replicar el procedimiento no debe tomarnos más de unos cuantos minutos.</p>



<p>Si bien este procedimiento ha sido desarrollado pensado en el despliegue de Jenkins, puede adaptarse fácilmente a cualquier tipo de aplicación conteinerizada, o simplemente para el despliegue correcto de un clúster Kubernetes sobre AWS.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f530.png" alt="🔰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Requerimientos Iniciales</h2>



<ul><li>Una cuenta activa de Amazon AWS.</li><li>El ordenador desde el cual se llevará a cabo el presente tutorial, debe tener instaladas y configuradas las siguientes herramientas:<ul><li><strong>Aws Cli</strong>: <a href="https://aws.amazon.com/cli/" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a>.</li><li><strong>eksctl</strong>: <a href="https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a>.</li><li><strong>kubectl</strong>: <a href="https://kubernetes.io/docs/tasks/tools/#kubectl" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a>.</li><li><strong>helm</strong>: <a href="https://helm.sh/docs/intro/install/" target="_blank" rel="noreferrer noopener nofollow">Guía de instalación</a>.</li></ul></li><li>Usando git, clonar localmente el siguiente repositorio de código en GitHub <strong><a href="https://github.com/jruizcampos/jenkins-k8s" target="_blank" rel="noreferrer noopener nofollow">Jenkins-K8s</a></strong>.</li></ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f309.png" alt="🌉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Arquitectura Jenkins sobre Kubernetes</h2>



<p>A continuación se muestra la arquitectura de componentes de la aplicación Jenkins sobre un clúster de Kubernetes EKS (AWS). </p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="877" height="451" src="https://johnruizcampos.com/wp-content/uploads/aws_kubernetes_eks_cluster_arquitecture.jpg" alt="kubernetes aws eks cluster jenkins" class="wp-image-331" srcset="https://johnruizcampos.com/wp-content/uploads/aws_kubernetes_eks_cluster_arquitecture.jpg 877w, https://johnruizcampos.com/wp-content/uploads/aws_kubernetes_eks_cluster_arquitecture-300x154.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_kubernetes_eks_cluster_arquitecture-768x395.jpg 768w" sizes="(max-width: 877px) 100vw, 877px" title="aws kubernetes eks cluster arquitecture"><figcaption>Arquitectura Jenkins sobre un Clúster de Kubernetes</figcaption></figure></div>



<p>En el diagrama se puede apreciar lo siguiente:</p>



<ul><li>Los recursos utilizados por Jenkins (pods, services, pvc, serviceAccount, ingress, etc. ) irán desplegados en su propio namespace, aislándolos de otros servicios / aplicaciones ya existentes en el clúster.</li><li>El deployment de Jenkins puede implementarse en uno o más pods brindando alta disponibilidad y escalamiento horizontal.</li><li>Los agentes Jenkins se levantan a demanda como pods y existen sólo durante el tiempo de ejecución del pipeline, luego de que éste finaliza el pod se destruye automáticamente y se liberan los recursos.</li><li> Si en un futuro el clúster se queda sin recursos de CPU / memoria, simplemente deben agregarse más nodos Kubernetes tipo Worker al clúster.</li><li>Los nodos tipo Worker en EKS por defecto usan el sistema operativo Amazon Linux 2.</li><li>El servicio de Jenkins será expuesto públicamente a través de un ingress, el cual a su vez está enlazado con un Application Load Balancer (ALB). Es a través de este ALB que podremos acceder a nuestra aplicación desde Internet.  </li></ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Despliegue de un Clúster Kubernetes en AWS EKS</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2699.png" alt="⚙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Desplegando el clúster con eksctl</h3>



<p>En mi experiencia, la manera más fácil para desplegar un clúster Kubernetes (EKS) en AWS es usando la herramienta <strong>eksctl</strong>.</p>



<p><strong>Eksctl</strong> nos creará de manera automática las plantillas de CloudFormation que desplegarán los recursos solicitados en AWS.</p>



<p>Teniendo <strong>eksctl</strong> correctamente instalado y configurado, ejecutar el siguiente comando en una terminal:</p>



<pre class="wp-block-code"><code><strong>eksctl</strong> create cluster <strong>--name</strong> "my-cluster" <strong>--region</strong> "us-east-1" <strong>--zones</strong> "us-east-1a,us-east-1b" <strong>--version</strong> 1.24 <strong>--node-type</strong> "t2.small" <strong>--nodes</strong> 2 <strong>--nodes-min</strong> 1 <strong>--nodes-max</strong> 2 --with-oidc --alb-ingress-access <strong>--spot</strong></code></pre>



<p>Vamos a explicar las principales opciones de la línea de comandos anterior:</p>



<ul><li><strong>&#8211;name</strong>: El nombre que llevará el clúster, en este caso <em>my-cluster</em>.</li><li><strong>&#8211;region</strong>: La región AWS sobre la que se desplegará el clúster, nosotros usaremos <em>us-east-1</em>.</li><li><strong>&#8211;zones</strong>: Las zonas de disponibilidad (subnets) dentro de la región sobre las que funcionará el clúster. Para nuestro caso y por simplicidad sólo seleccionamos 2, pero pueden ser más.</li><li><strong>&#8211;version</strong>: La versión de Kubernetes a utilizar.</li><li><strong>&#8211;node-type</strong>: El tipo de instancia que se utilizará para desplegar los nodos. En nuestro ejemplo usaremos <em>t2.small</em>.</li><li><strong>&#8211;nodes, &#8211;nodes-min, &#8211;nodes-max:</strong> Número de nodos inicial, mínimo y máximo que tendrá el clúster </li><li><strong>&#8211;spot</strong> (Opcional): Desplegará las instancias EC2 como tipo spot ahorrando costos. No usar esta opción en despliegues para producción.</li></ul>



<p>Al ejecutar esta línea de comando, veremos una salida como la siguiente:</p>



<pre class="wp-block-code" style="font-size:13px"><code>14:38:34  eksctl version 0.122.0
14:38:34  using region us-east-1
14:38:34  subnets for us-east-1a - public:192.168.0.0/19 private:192.168.64.0/19
14:38:34  subnets for us-east-1b - public:192.168.32.0/19 private:192.168.96.0/19
14:38:34  nodegroup "ng-0e78de48" will use "" &#91;AmazonLinux2/1.24]
14:38:34  using Kubernetes version 1.24
14:38:34  creating EKS cluster "my-cluster" in "us-east-1" region with managed nodes
14:38:34  will create 2 separate CloudFormation stacks for cluster itself and the initial managed nodegroup
14:38:34  if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=us-east-1 --cluster=my-cluster'
14:38:34  Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "my-cluster" in "us-east-1"
.
.
14:57:04  waiting for CloudFormation stack "eksctl-my-cluster-nodegroup-ng-0e78de48"
14:57:04  waiting for the control plane to become ready
14:57:05 &#91;&#x2714;]  saved kubeconfig as "C:\\Users\\John\\.kube\\config"
14:57:05  no tasks
14:57:05 &#91;&#x2714;]  all EKS cluster resources for "my-cluster" have been created
14:57:06  nodegroup "ng-0e78de48" has 2 node(s)
14:57:06  node "ip-192-168-32-202.ec2.internal" is ready
14:57:06  node "ip-192-168-5-170.ec2.internal" is ready
14:57:06  waiting for at least 1 node(s) to become ready in "ng-0e78de48"
14:57:10  kubectl command should work with "C:\\Users\\John\\.kube\\config", try 'kubectl get nodes'
14:57:10 &#91;&#x2714;]  EKS cluster "my-cluster" in "us-east-1" region is ready</code></pre>



<p>Al terminar el despliegue, vamos a la consola de AWS y verificamos que nuestro clúster EKS se haya desplegado correctamente:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="694" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_initial-1024x694.jpg" alt="Clúster AWS EKS Kubernetes" class="wp-image-261" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_initial-1024x694.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_initial-300x203.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_initial-768x520.jpg 768w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_initial.jpg 1064w" sizes="(max-width: 1024px) 100vw, 1024px" title="aws eks cluster initial"><figcaption>Clúster EKS recién desplegado</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Configurando la conexión al clúster Kubernetes</h3>



<p>Para acceder a nuestro nuevo clúster, ya sea por línea de comandos <strong>eksctl</strong> o de manera gráfica usando <strong><a href="https://k8slens.dev/" target="_blank" rel="noreferrer noopener nofollow">Lens</a></strong>, debemos actualizar nuestro archivo local de configuración de Kubernetes (<em>~/.kube/config</em>) con la información correspondiente de conexión. Para ello ejecutamos el siguiente comando:</p>



<pre class="wp-block-code" style="font-size:18px"><code><strong>aws eks update-kubeconfig --region "us-east-1" --name "my-cluster"</strong></code></pre>



<pre class="wp-block-code has-small-font-size"><code>Updated context arn:aws:eks:us-east-1:680655248338:cluster/my-cluster in C:\Users\John\.kube\config</code></pre>



<p>En el parámetro <strong>&#8211;region</strong> colocamos la región en la cual desplegamos nuestro clúster, y en <strong>&#8211;name</strong> el nombre del mismo.</p>



<p>A continuación verificamos que podamos conectarnos correctamente al clúster usando <strong>kubectl</strong>:</p>



<pre class="wp-block-code" style="font-size:19px"><code><strong>kubectl cluster-info</strong></code></pre>



<pre class="wp-block-code has-small-font-size"><code>Kubernetes control plane is running at https://******.gr7.us-east-1.eks.amazonaws.com
CoreDNS is running at https://******.gr7.us-east-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.</code></pre>



<pre class="wp-block-code" style="font-size:19px"><code><strong>kubectl get nodes</strong></code></pre>



<pre class="wp-block-code has-small-font-size"><code>NAME                             STATUS   ROLES    AGE    VERSION
ip-192-168-32-202.ec2.internal   Ready    &lt;none&gt;   113m   v1.24.7-eks-fb459a0
ip-192-168-5-170.ec2.internal    Ready    &lt;none&gt;   113m   v1.24.7-eks-fb459a0</code></pre>



<p>Para el caso de <strong><a href="https://k8slens.dev/" target="_blank" rel="noreferrer noopener nofollow">Lens</a></strong>, una nueva conexión aparecerá en la lista de clústeres disponibles:</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="718" height="214" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_lens.jpg" alt="EKS K8s Lens" class="wp-image-279" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_lens.jpg 718w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_lens-300x89.jpg 300w" sizes="(max-width: 718px) 100vw, 718px" title="aws eks cluster lens"><figcaption>Accediendo al clúster Kubernetes usando Lens</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4bd.png" alt="💽" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalación del controlador Amazon EBS CSI </h3>



<p>Para poder aprovisionar volúmenes de almacenamiento (unidades de disco) en nuestro clúster, es necesario que instalemos un controlador de almacenamiento.</p>



<p>Para AWS EKS instalaremos el controlador <a href="https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html" target="_blank" rel="noreferrer noopener nofollow"><strong>Amazon EBS CSI</strong></a> (<em>Elastic Block Store Container Storage Interface</em>). Este driver nos permitirá crear y eliminar dinámicamente volúmenes EBS (Elastic Block Store) para ser utilizados por las aplicaciones en nuestro clúster.</p>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f468-200d-1f4bc.png" alt="👨‍💼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Creación del rol de IAM</h4>



<p>Primero debemos crear el rol de IAM con el cual se ejecutará el controlador <strong>EBS CSI</strong> en el clúster. Para ello, en el siguiente comando reemplazamos <em><strong>my-cluster</strong></em> por el nombre de nuestro clúster y lo ejecutamos:</p>



<pre class="wp-block-code has-small-font-size"><code>eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster <strong>my-cluster</strong> \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole</code></pre>



<p>Verificamos que el comando termine de ejecutarse correctamente:</p>



<pre class="wp-block-code has-small-font-size"><code>17:35:34  1 existing iamserviceaccount(s) (kube-system/aws-node) will be excluded
17:35:34  1 iamserviceaccount (kube-system/ebs-csi-controller-sa) was included (based on the include/exclude rules)
17:35:34  serviceaccounts that exist in Kubernetes will be excluded, use --override-existing-serviceaccounts to override
17:35:34  1 task: { create IAM role for serviceaccount "kube-system/ebs-csi-controller-sa" }
17:35:34  building iamserviceaccount stack "eksctl-my-cluster-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
17:35:34  deploying stack "eksctl-my-cluster-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
17:35:34  waiting for CloudFormation stack "eksctl-my-cluster-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1fa9b.png" alt="🪛" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalación del controlador usando eksctl o AWS CLI</h4>



<p>Ahora realizaremos la instalación del controlador en sí. En el siguiente comando, reemplazamos <em><strong>my-cluster</strong></em> por el nombre de nuestro clúster y el valor de <em><strong>111122223333</strong></em> por nuestro identificador de cuenta de AWS:</p>



<pre class="wp-block-code has-small-font-size"><code>eksctl create addon --name aws-ebs-csi-driver --cluster <strong>my-cluster</strong> --service-account-role-arn arn:aws:iam::<strong>111122223333</strong>:role/AmazonEKS_EBS_CSI_DriverRole</code></pre>



<p>También podemos realizar la misma tarea usando AWS CLI:</p>



<pre class="wp-block-code has-small-font-size"><code>aws eks create-addon --cluster-name <strong>my-cluster</strong> --addon-name aws-ebs-csi-driver --service-account-role-arn arn:aws:iam::<strong>111122223333</strong>:role/AmazonEKS_EBS_CSI_DriverRole</code></pre>



<p>Verificamos que el driver haya terminado de instalarse correctamente:</p>



<pre class="wp-block-code has-small-font-size"><code>2023-01-08 11:53:31 &#91;&#x2139;]  Kubernetes version "1.24" in use by cluster "my-cluster"
2023-01-08 11:53:32 &#91;&#x2139;]  using provided ServiceAccountRoleARN "arn:aws:iam::680655248338:role/AmazonEKS_EBS_CSI_DriverRole"
2023-01-08 11:53:32 &#91;&#x2139;]  creating addon</code></pre>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="614" height="312" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_ebs_csi_driver.jpg" alt="AWS EKS EBS CSI Driver" class="wp-image-285" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_ebs_csi_driver.jpg 614w, https://johnruizcampos.com/wp-content/uploads/aws_eks_ebs_csi_driver-300x152.jpg 300w" sizes="(max-width: 614px) 100vw, 614px" title="aws eks ebs csi driver"><figcaption>Driver Amazon EBS CSI instalándose</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalación de Prometheus para el monitoreo del clúster</h3>



<p>Para el monitoreo básico del estado de nuestro clúster, instalaremos la aplicación Prometheus. Esto lo realizaremos usando el comando <strong>helm</strong>.</p>



<p>Primero agregamos el repositorio de charts de Prometheus y actualizamos nuestra caché local:</p>



<pre class="wp-block-code" style="font-size:15px"><code>helm repo add prometheus-community https://prometheus-community.github.io/helm-charts</code></pre>



<pre class="wp-block-code has-small-font-size"><code>helm repo update</code></pre>



<p>Instalamos el chart de Prometheus:</p>



<pre class="wp-block-code has-small-font-size"><code>helm install prometheus prometheus-community/prometheus</code></pre>



<p>Una vez Prometheus esté instalado, podremos ver en los widgets de monitoreo de Lens información de uso de recursos como CPU, memoria, etc.:</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="781" height="366" src="https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_1.jpg" alt="consumo cpu memoria kubernetes prometheus" class="wp-image-313" srcset="https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_1.jpg 781w, https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_1-300x141.jpg 300w, https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_1-768x360.jpg 768w" sizes="(max-width: 781px) 100vw, 781px" title="kubernetes lens prometheus 1"><figcaption>Widgets de consumo de CPU y Memoria del Clúster después de instalar Prometheus</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="838" height="158" src="https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_2.jpg" alt="kubernetes lens prometheus 2" class="wp-image-314" srcset="https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_2.jpg 838w, https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_2-300x57.jpg 300w, https://johnruizcampos.com/wp-content/uploads/kubernetes_lens_prometheus_2-768x145.jpg 768w" sizes="(max-width: 838px) 100vw, 838px" title="kubernetes lens prometheus 2"><figcaption>Consumo de CPU y Memoria por Nodo después de instalar Prometheus</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalación del controlador AWS Load Balancer</h3>



<p>Para poder publicar nuestros servicios en Kubernetes y así estén disponibles en Internet, es necesario contar con un controlador que mapee estos servicios con un balanceador de carga correspondiente al proveedor de cloud que estemos utilizando.</p>



<p>Para el caso de AWS es necesario instalar el <a href="https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html" target="_blank" rel="noreferrer noopener nofollow"><strong>AWS Load Balancer Controller</strong></a>. Este controlador nos permitirá mapear un ingress o servicio de Kubernetes a un Application Load Balancer (ALB) o Network Load Balancer (NLB), según sea el caso.</p>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f468-200d-1f4bc.png" alt="👨‍💼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Creación de la política y rol de IAM </h4>



<p>Primero creamos la política que utilizará el rol. Ejecutamos:</p>



<pre class="wp-block-code has-small-font-size"><code>curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/install/iam_policy.json</code></pre>



<pre class="wp-block-code has-small-font-size"><code>aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json</code></pre>



<p>Creamos el rol IAM y Service Account con el cual se ejecutará el controlador a instalar. Reemplazamos <strong><em>my-cluster</em></strong> con el nombre de nuestro clúster y el valor de <strong><em>111122223333</em></strong> lo reemplazamos por nuestro identificador de cuenta de AWS. Ejecutamos:</p>



<pre class="wp-block-code has-small-font-size"><code>eksctl create iamserviceaccount \
  --cluster=<strong>my-cluster</strong> \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --role-name "AmazonEKSLoadBalancerControllerRole" \
  --attach-policy-arn=arn:aws:iam::<strong>111122223333</strong>:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1fa9b.png" alt="🪛" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalación del controlador con Helm</h4>



<p>Instalaremos el controlador usando <strong>helm</strong>. Primero agregamos el Helm chart correspondiente y actualizamos:</p>



<pre class="wp-block-code has-small-font-size"><code>helm repo add eks https://aws.github.io/eks-charts</code></pre>



<pre class="wp-block-code has-small-font-size"><code>helm repo update</code></pre>



<p>En el siguiente comando, reemplazamos <strong><em>my-cluster</em></strong> con el nombre de nuestro clúster, los valores  <strong><em>602401143452</em></strong> y <strong><em>us-east-1</em></strong> debes reemplazarlos con los valores correspondientes a tu caso según el <a href="https://docs.aws.amazon.com/eks/latest/userguide/add-ons-images.html" target="_blank" rel="noreferrer noopener nofollow"><strong>siguiente cuadro</strong></a>. Ejecutamos el comando y verificamos que termine correctamente.</p>



<pre class="wp-block-code has-small-font-size"><code>helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=<strong>my-cluster</strong> \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  --set image.repository=<strong>602401143452</strong>.dkr.ecr.<strong>us-east-1</strong>.amazonaws.com/amazon/aws-load-balancer-controller</code></pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Despliegue de Jenkins en Kubernetes</h2>



<p>Ya tenemos nuestro clúster Kubernetes ejecutándose y con todos los plugins necesarios, así que ya podemos proceder a instalar Jenkins.</p>



<p>Si aún no lo haz hecho, clona el repositorio de código en GitHub correspondiente al presente artículo:</p>



<pre class="wp-block-code has-small-font-size"><code>git clone https://github.com/jruizcampos/jenkins-k8s.git</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f529.png" alt="🔩" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Creando los recursos de Jenkins en Kubernetes</h3>



<p>Ubicados en la carpeta del repositorio clonado, ejecutamos los siguientes comandos. Creamos el <em>namespace</em>, <em>Service Account</em> y <em>volumen</em> necesarios:</p>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f namespace.yaml</code></pre>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f serviceAccount.yaml</code></pre>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f volume.yaml</code></pre>



<p>Creamos el <em>deployment</em> de Jenkins y su servicio de acceso correspondiente:</p>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f deployment.yaml</code></pre>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f service.yaml</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2693.png" alt="⚓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Configurando el Ingress y Load Balancer en AWS</h3>



<p>Para configurar el ingress en Kubernetes, necesitamos la lista de subnets públicas configuradas en nuestro clúster EKS. Para ello, desde la consola de AWS vamos al servicio de <strong>VPC</strong>, seleccionamos <strong>Subnets</strong> y filtramos las redes por «<em>public</em>«. Copiamos los <strong>Subnet ID</strong> encontrados:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="230" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_5-1024x230.jpg" alt="EKS Cluster Subnets" class="wp-image-236" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_5-1024x230.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_5-300x68.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_5-768x173.jpg 768w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_5.jpg 1040w" sizes="(max-width: 1024px) 100vw, 1024px" title="aws eks cluster k8s 5"><figcaption>Listando las subnets públicas del Clúster EKS</figcaption></figure></div>



<p>Editamos el archivo <em>ingress.yaml</em> y buscamos la sección  <strong>alb.ingress.kubernetes.io/subnets</strong>, a continuación actualizamos los valores con la lista de <em>Subnet ID</em>&#8216;s obtenida en el paso anterior:</p>



<pre class="wp-block-code has-small-font-size"><code>  alb.ingress.kubernetes.io/subnets: subnet-0ce7fbd629a3db193,subnet-0c4b6807f56cbd85d</code></pre>



<p>Procedemos a crear el ingress:</p>



<pre class="wp-block-code has-small-font-size"><code>kubectl apply -f ingress.yaml</code></pre>



<pre class="wp-block-code has-small-font-size"><code>ingress.networking.k8s.io/jenkins-ingress created</code></pre>



<p>Luego de unos minutos, verificamos que el load balancer correspondiente se haya creado en AWS. Vamos a la consola <strong>EC2</strong> de AWS y seleccionamos <strong>Load Balancers</strong>. El load balancer creado debe ser del tipo <em>application</em> (ALB):</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="287" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_6-1024x287.jpg" alt="Application Load Balancer for EKS" class="wp-image-237" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_6-1024x287.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_6-300x84.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_6-768x215.jpg 768w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_6.jpg 1157w" sizes="(max-width: 1024px) 100vw, 1024px" title="aws eks cluster k8s 6"><figcaption>Application Load Balancer creado por el ingress desplegado (archivo ingress.yaml)</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f30e.png" alt="🌎" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Comprobando el acceso a Jenkins</h3>



<p>En el Load Balancer creado en el paso anterior verificamos su <strong>DNS name</strong>, lo copiamos y pegamos en la barra de direcciones de nuestro navegador web y accedemos:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="401" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_7-1024x401.jpg" alt="Jenkins DNS Name" class="wp-image-238" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_7-1024x401.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_7-300x118.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_7-768x301.jpg 768w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_7.jpg 1176w" sizes="(max-width: 1024px) 100vw, 1024px" title="aws eks cluster k8s 7"><figcaption>Accediendo a Jenkins mediante el DNS Name del Load Balancer</figcaption></figure></div>



<p>Se nos pedirá la clave de administrador inicial generada durante el momento de la instalación. Para obtener esta clave, debemos inspeccionar los logs del pod de despliegue de Jenkins.</p>



<p>Primero listamos los pods ejecutándose actualmente en el namespace <em>devops-tools</em>:</p>



<pre class="wp-block-code has-small-font-size"><code>kubectl get pods -n devops-tools</code></pre>



<pre class="wp-block-code has-small-font-size"><code>NAME                       READY   STATUS    RESTARTS   AGE
jenkins-5ddc766476-ll82g   1/1     Running   0          88m</code></pre>



<p>Una vez identificado el pod, obtenemos las últimas 20 líneas de su log de ejecución:</p>



<pre class="wp-block-code has-small-font-size"><code>kubectl logs jenkins-5ddc766476-ll82g --tail 20 -n devops-tools</code></pre>



<p>En el fragmento de log mostrado, se puede apreciar claramente la clave generada:</p>



<pre class="wp-block-code has-small-font-size"><code>*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

<strong>a14f39190fa44226af623ce9560b0d7b</strong>

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************</code></pre>



<p>Ingresamos esta clave en la página de inicio de Jenkins, instalamos la lista de plugins recomendados y creamos un usuario administrador:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="571" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_10-1024x571.jpg" alt="Jenkins install suggested plugins" class="wp-image-241" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_10-1024x571.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_10-300x167.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_10-768x428.jpg 768w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_10.jpg 1166w" sizes="(max-width: 1024px) 100vw, 1024px" title="aws eks cluster k8s 10"><figcaption>Instalación de plugins recomendados de Jenkins</figcaption></figure></div>



<p>Configuramos la URL de acceso a nuestra instancia e ingresamos a la consola de Jenkins:</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="899" height="391" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_12-e1673367137203.jpg" alt="Jenkins URL de acceso" class="wp-image-243" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_12-e1673367137203.jpg 899w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_12-e1673367137203-300x130.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_12-e1673367137203-768x334.jpg 768w" sizes="(max-width: 899px) 100vw, 899px" title="aws eks cluster k8s 12 e1673367137203"><figcaption>Configurando la URL de acceso a Jenkins</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="979" height="639" src="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_13.jpg" alt="Jenkins first run" class="wp-image-244" srcset="https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_13.jpg 979w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_13-300x196.jpg 300w, https://johnruizcampos.com/wp-content/uploads/aws_eks_cluster_k8s_13-768x501.jpg 768w" sizes="(max-width: 979px) 100vw, 979px" title="aws eks cluster k8s 13"><figcaption>Consola de Jenkins recién instalado</figcaption></figure></div>



<p> </p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Como instalar Kubernetes en Linux con Ansible</title>
		<link>https://johnruizcampos.com/como-instalar-kubernetes-en-linux-con-ansible/</link>
		
		<dc:creator><![CDATA[John Ruiz]]></dc:creator>
		<pubDate>Sat, 13 Nov 2021 19:48:45 +0000</pubDate>
				<category><![CDATA[Sin categoría]]></category>
		<guid isPermaLink="false">https://johnruizcampos.com/?p=50</guid>

					<description><![CDATA[El presente proyecto parte de la necesidad que tuve hace unos meses de instalar un clúster Kubernetes de manera on-premise (bare metal) para un trabajo particular. En mi opinión, el despliegue de Kubernetes desde cero&#8230;]]></description>
										<content:encoded><![CDATA[
<p>El presente proyecto parte de la necesidad que tuve hace unos meses de instalar un clúster Kubernetes de manera on-premise (bare metal) para un trabajo particular. En mi opinión, el despliegue de Kubernetes desde cero es complejo, toma tiempo y requiere de mucha habilidad y conocimiento técnico de parte del administrador.</p>



<p>Es por ello que una vez logré realizarlo con éxito, decidí recopilar todas la tareas y configuraciones necesarias en un <a href="https://github.com/jruizcampos/kubernetes-deploy" target="_blank" rel="noreferrer noopener nofollow">proyecto Ansible</a>, de manera que pudiera reutilizar el procedimiento cuando lo necesitase, además de quedar documentado para el futuro.</p>



<p>En el presente artículo trato de explicar el procedimiento de instalación de un clúster Kubernetes en Linux usando Ansible, el despliegue de aplicaciones simples, así como también el uso de comandos para verficar el correcto funcionamiento de nodos, deployments, servicios, ingress, etc. en Kubernetes. </p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f530.png" alt="🔰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Requerimientos Iniciales</h2>



<p>Para la ejecución del proyecto Ansible necesitaremos como mínimo 3 hosts Linux (Ansible, Master y Worker), los cuales tendrán las siguientes funciones y características:</p>



<figure class="wp-block-table alignwide is-style-stripes"><table><thead><tr><th class="has-text-align-center" data-align="center">Servidor</th><th class="has-text-align-center" data-align="center">Función</th><th class="has-text-align-center" data-align="center">Software</th><th class="has-text-align-center" data-align="center">Hardware</th></tr></thead><tbody><tr><td class="has-text-align-center" data-align="center">Ansible</td><td class="has-text-align-center" data-align="center">Controlador Ansible desde el cual se realizará el despliegue.</td><td class="has-text-align-center" data-align="center">Centos 7/8<br>Rocky Linux 8<br>Debe tener instalado Ansible y GIT</td><td class="has-text-align-center" data-align="center">2 CPU<br>2 GB RAM</td></tr><tr><td class="has-text-align-center" data-align="center">Master</td><td class="has-text-align-center" data-align="center">Realizará la función de Kubernetes Master</td><td class="has-text-align-center" data-align="center">Centos 7/8<br>Rocky Linux 8<br>Instalación Limpia</td><td class="has-text-align-center" data-align="center">2 CPU<br>4 GB RAM</td></tr><tr><td class="has-text-align-center" data-align="center">Worker</td><td class="has-text-align-center" data-align="center">Realizará la función de Kubernetes Worker.<br>Podemos desplegar 1 o más.</td><td class="has-text-align-center" data-align="center">Centos 7/8<br>Rocky Linux 8<br>Instalación Limpia</td><td class="has-text-align-center" data-align="center">2 CPU<br>4 GB RAM</td></tr><tr><td class="has-text-align-center" data-align="center">NFS<br>(Opcional)</td><td class="has-text-align-center" data-align="center">Ejecutará la función de Servidor NFS<br>Podemos reutilizar el host Master para esta función</td><td class="has-text-align-center" data-align="center">Centos 7/8<br>Rocky Linux 8<br>Instalación Limpia</td><td class="has-text-align-center" data-align="center">2 CPU<br>2 GB RAM</td></tr></tbody></table></figure>



<ul><li>Para todos los servidores podemos usar CentOS 7/8 o <a aria-label="Rocky Linux 8 (opens in a new tab)" href="https://rockylinux.org/" target="_blank" rel="noreferrer noopener nofollow" class="rank-math-link">Rocky Linux 8</a>.</li><li>El servidor Ansible funcionará como controlador y debe tener Ansible y el cliente de GIT instalados:</li></ul>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ sudo yum install epel-release
&#91;adminUsername@localhost ~]$ sudo yum install ansible
&#91;adminUsername@localhost ~]$ sudo yum install git
&#91;adminUsername@localhost ~]$
&#91;adminUsername@localhost ~]$ git --version
git version 2.27.0
&#91;adminUsername@localhost ~]$ ansible --version
ansible 2.9.25</code></pre>



<ul><li>Para los servidores Master, Worker y NFS, el sistema operativo debe estar recién instalado, actualizado y sin paquetes ni software adicional.</li></ul>



<ul><li>Para el presente proyecto se recomienda desplegar un sólo servidor Master y NFS. Podemos desplegar tantos servidores Worker como deseemos y los recursos nos lo permitan.</li></ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f309.png" alt="🌉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Infraestructura de Despliegue</h2>



<p>Todas los servidores se encontrarán conectados al mismo segmento de red (en este caso <strong>10.0.1.0/24</strong>) según la imagen:</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img decoding="async" width="883" height="463" src="https://johnruizcampos.com/wp-content/uploads/red_kubernetes_ansible.jpg" alt="Instalar Kubernetes con Ansible" class="wp-image-69" srcset="https://johnruizcampos.com/wp-content/uploads/red_kubernetes_ansible.jpg 883w, https://johnruizcampos.com/wp-content/uploads/red_kubernetes_ansible-300x157.jpg 300w, https://johnruizcampos.com/wp-content/uploads/red_kubernetes_ansible-768x403.jpg 768w" sizes="(max-width: 883px) 100vw, 883px" title="red kubernetes ansible"><figcaption>Diagrama de la Infraestructura Kubernetes</figcaption></figure></div>



<p>Ya con los servidores preparados, procederemos a explicar el procedimiento de despliegue:</p>



<p>En el host Ansible descargaremos el proyecto Ansible con <strong>git</strong>, personalizaremos algunos archivos, configuraremos el acceso a los hosts Kubernetes (master, workers y nfs) y lanzaremos el despliegue sobre dichos servidores.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f9d0.png" alt="🧐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Estructura del Proyecto Ansible</h2>



<p>El proyecto Ansible que utilizaremos se encuentra disponible en el siguiente repositorio GitHub: <a aria-label="https://github.com/jruizcampos/kubernetes-deploy (opens in a new tab)" href="https://github.com/jruizcampos/kubernetes-deploy" target="_blank" rel="noreferrer noopener nofollow" class="rank-math-link"><strong>https://github.com/jruizcampos/kubernetes-deploy</strong></a> </p>



<p>Procedemos a clonar el repositorio:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ git clone https://github.com/jruizcampos/kubernetes-deploy.git
Cloning into 'kubernetes-deploy'...
remote: Enumerating objects: 730, done.
remote: Counting objects: 100% (730/730), done.
remote: Compressing objects: 100% (432/432), done.
remote: Total 730 (delta 341), reused 513 (delta 140), pack-reused 0
Receiving objects: 100% (730/730), 158.54 KiB | 418.00 KiB/s, done.
Resolving deltas: 100% (341/341), done.</code></pre>



<p>Ingresamos al proyecto, carpeta ansible y revisamos su contenido:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ cd kubernetes-deploy/ansible/
&#91;adminUsername@localhost ansible]$ ls -lh
total 40K
-rw-rw-r--. 1 john john 171 Nov  8 05:51 01-configuraciones-comunes.yml
-rw-rw-r--. 1 john john 150 Nov  8 05:51 02-configurando-nfs.yml
-rw-rw-r--. 1 john john 204 Nov  8 05:51 03-configuraciones-master-worker.yml
-rw-rw-r--. 1 john john 163 Nov  8 05:51 04-configurando-master.yml
-rw-rw-r--. 1 john john 166 Nov  8 05:51 05-configurando-workers.yml
-rw-rw-r--. 1 john john 155 Nov  8 05:51 06-tests-cluster.yml
-rwxrwxr-x. 1 john john 228 Nov  8 05:51 deploy.sh
-rw-rw-r--. 1 john john 173 Nov  8 05:51 despliegue-aplicacion.yml
-rw-rw-r--. 1 john john 755 Nov  8 05:51 despliegue-kubernetes.yml
drwxrwxr-x. 2 john john 141 Nov  8 05:51 group_vars
-rw-rw-r--. 1 john john 157 Nov  8 05:51 hosts
drwxrwxr-x. 9 john john 106 Nov  8 05:51 roles
</code></pre>



<p>De la lista, los 2 playbooks principales son <strong>despliegue-kubernetes.yml</strong> y <strong>despliegue-aplicacion.yml</strong>. Estos 2 playbooks llaman o usan todos los demás archivos del proyecto según el siguiente esquema:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" width="1024" height="391" src="https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes-1024x391.jpg" alt="Proyecto Ansible Kubernetes" class="wp-image-68" srcset="https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes-1024x391.jpg 1024w, https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes-300x114.jpg 300w, https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes-768x293.jpg 768w, https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes-1536x586.jpg 1536w, https://johnruizcampos.com/wp-content/uploads/proyecto_ansible_kubernetes.jpg 1772w" sizes="(max-width: 1024px) 100vw, 1024px" title="proyecto ansible kubernetes"><figcaption>Estructura del Proyecto Ansible</figcaption></figure></div>



<p>El playbook <strong>despliegue-kubernetes.yml</strong> se encarga del despliegue del clúster Kubernetes en sí, y el playbook <strong>despliegue-aplicacion.yml</strong> instala de manera opcional un par de aplicaciones de ejemplo sobre el clúster ya instalado.</p>



<p>El script bash <strong>deploy.sh</strong> es sólo una manera cómoda de llamar a ambos playbooks de manera secuencial.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f39a.png" alt="🎚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Personalizando el Proyecto</h2>



<p>Antes de realizar el despliegue, debemos editar un par de archivos con valores acordes a nuestra infraestructura en particular. </p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Archivo <strong>ansible/hosts</strong>:</h3>



<pre class="wp-block-code"><code>&#91;all:vars]
# Usuario linux para conectarse a los hosts
ansible_user=adminUsername

# Definimos el host master y su ip
&#91;master]
master1 ansible_host=10.0.1.21

# Definimos los hosts worker y sus ips
&#91;workers]
worker1 ansible_host=10.0.1.51
# worker2 ansible_host=10.0.1.52
# .
# .
# .
# worker&#91;n] ansible_host=10.0.1.n

# Definimos el host NFS y su ip
&#91;nfs]
nfs1 ansible_host=10.0.1.21</code></pre>



<p>Este archivo debemos modificarlo de acuerdo a lo siguiente:</p>



<pre class="wp-block-code"><code>ansible_user=adminUsername</code></pre>



<p>En esta sección colocaremos en <strong>ansible_user </strong>el Usuario Linux que Ansible utilizará para conectarse a los hosts [master], [workers] y [nfs] (en este caso <em>adminUsername</em>). Debemos colocar el que nosotros hayamos configurado en dichos sistemas.</p>



<pre class="wp-block-code"><code>&#91;master]
master1 ansible_host=10.0.1.21</code></pre>



<p>En esta sección colocaremos en <strong>ansible_host</strong> la dirección ip del host <em>master1</em> (en este caso <em>10.0.1.21</em>).</p>



<pre class="wp-block-code"><code>&#91;workers]
worker1 ansible_host=10.0.1.51
worker2 ansible_host=10.0.1.52
...</code></pre>



<p>En esta sección colocaremos en <strong>ansible_host</strong> la dirección ip de los hosts <em>worker1</em>, <em>worker2</em>, etc. En este caso <em>10.0.1.51</em>, <em>10.0.1.52</em>, etc. </p>



<pre class="wp-block-code"><code>&#91;nfs]
nfs1 ansible_host=10.0.1.21</code></pre>



<p>En esta sección colocaremos en <strong>ansible_host</strong> la dirección ip del host nfs. En este caso <em>10.0.1.21</em> (estamos reutilizando el master para la función nfs).</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Archivo <strong>ansible/group_vars/master.yaml</strong></h3>



<pre class="wp-block-code has-small-font-size"><code># Red de Docker configurada en el master. La obtenemos a través de los facts
docker_network: "{{ ansible_docker0.ipv4.network }}/16"

# Aquí definimos la red por defecto para los pods. Podemos cambiarla si así lo deseamos.
pod_network: "10.255.0.0/16"

# Aquí definimos que tipo de SDN queremos desplegar Calico o Flannel
sdn: flannel
# sdn: calico</code></pre>



<p>En este archivo modificaremos parámetros relacionados a la instalación de Kubernetes de acuerdo a lo siguiente:</p>



<pre class="wp-block-code"><code>pod_network: "10.255.0.0/16"</code></pre>



<p>En esta sección colocaremos en <strong>pod_network</strong> el segmento de red de los pods en formato CIDR. Este valor por lo general no se cambia y se usa el que está por defecto: <em>10.255.0.0/16</em></p>



<pre class="wp-block-code"><code>sdn: calico</code></pre>



<p>En esta sección colocaremos en <strong>sdn</strong> el tipo de SDN a utilizar en la instalación de Kubernetes: <strong>calico </strong>o <strong>flannel</strong> (en minúsculas).</p>



<p>La elección de <a href="https://docs.projectcalico.org/getting-started/kubernetes/quickstart" target="_blank" aria-label="Calico (opens in a new tab)" rel="noreferrer noopener nofollow" class="rank-math-link">Calico</a> o <a href="https://docs.projectcalico.org/getting-started/kubernetes/flannel/flannel" target="_blank" aria-label="Flannel (opens in a new tab)" rel="noreferrer noopener nofollow" class="rank-math-link">Flannel</a> depende de la infraestructura subyacente sobre la cual estamos instalando Kubernetes:</p>



<ol><li>Si estamos realizando una instalación on-premise (bare-metal) se recomienda usar <strong>Calico </strong>puesto que es más completo y personalizable (aunque Flannel también funcionará).</li><li>Si estamos realizando una instalación sobre máquinas virtuales desplegadas en un proveedor de cloud como Azure, debemos usar <strong>Flannel </strong>puesto que los proveedores de cloud por defecto bloquean a nivel de red ciertos puertos y protocolos necesarios para el correcto funcionamiento de Calico.</li></ol>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Configurando los permisos de Ansible</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f511.png" alt="🔑" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Configurando el acceso directo por SSH a los hosts</h3>



<p>Antes de proceder con el despliegue, debemos configurar el acceso por SSH desde el host Ansible hacia los hosts master y workers sin necesidad de ingresar usuario y contraseña.</p>



<p>Para ello, en nuestro host Ansible debemos contar con las llaves (keys) SSH privada y pública de nuestro usuario Linux. En caso no se encuentren creadas, podemos hacerlo con el comando <strong>ssh-keygen</strong>:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/adminUsername/.ssh/id_rsa):
Created directory '/home/adminUsername/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/adminUsername/.ssh/id_rsa.
Your public key has been saved in /home/adminUsername/.ssh/id_rsa.pub.</code></pre>



<p>Ahora procederemos a copiar nuestra llave pública a los hosts master y worker:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;localhost ~]$ ssh-copy-id -i ~/.ssh/id_rsa.pub adminUsername@10.0.1.21
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/adminUsername/.ssh/id_rsa.pub"
The authenticity of host '10.0.1.21 (10.0.1.21)' can't be established.
Are you sure you want to continue connecting (yes/no/&#91;fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
adminUsername@10.0.1.21's password:

Number of key(s) added: 1

&#91;localhost ~]$ ssh-copy-id -i ~/.ssh/id_rsa.pub adminUsername@10.0.1.51
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/adminUsername/.ssh/id_rsa.pub"
The authenticity of host '10.0.1.51 (10.0.1.51)' can't be established.
Are you sure you want to continue connecting (yes/no/&#91;fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
adminUsername@10.0.1.51's password:

Number of key(s) added: 1</code></pre>



<p>De esta manera, ya podemos iniciar sesión en dichos servidores sin necesidad de usar usuario y clave. Podemos verificarlo con los comandos a continuación:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ ssh adminUsername@10.0.1.21
&#91;adminUsername@localhost ~]$
&#91;adminUsername@localhost ~]$ ssh adminUsername@10.0.1.51
&#91;adminUsername@localhost ~]$</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Configurando visudo en los hosts Kubernetes</h3>



<p>Adicionalmente, debemos habilitar en los hosts <em>master</em>, <em>workers</em> y <em>nfs</em> que el usuario Linux pueda escalar privilegios mediante <em>sudo</em> pero sin solicitar la contraseña.</p>



<p>Para ello, en cada servidor ejecutamos el comando <strong>visudo</strong> como root:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ sudo visudo</code></pre>



<p>Agregamos la siguiente línea al final del archivo y guardamos:</p>



<pre class="wp-block-code has-small-font-size"><code>## Same thing without a password
adminUsername    ALL=(ALL)       NOPASSWD: ALL</code></pre>



<p>Ahora si probamos escalar privilegios como superusuario (root), veremos que ya no se nos pide la contraseña:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;adminUsername@localhost ~]$ sudo su
&#91;root@localhost adminUsername]#</code></pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Despliegue del Clúster Kubernetes</h2>



<p>Ya estamos listos para realizar el despliegue de nuestro clúster Kubernetes.</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2699.png" alt="⚙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalando Kubernetes</h3>



<p>Ingresamos a la carpeta <strong>kubernetes-deploy/ansible</strong> y ejecutamos lo siguiente:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;localhost ansible]$ ansible-playbook -i hosts despliegue-kubernetes.yml</code></pre>



<p>Mientras se ejecuta el playbook, se mostrarán una a una las tareas que se van realizando sobre los hosts. A continuación un resumen:</p>



<pre class="wp-block-code has-small-font-size"><code>PLAY &#91;Realizando configuraciones comunes a todos los nodos] ***********************

TASK &#91;Gathering Facts] ************************************************************
ok: &#91;master1]
ok: &#91;nfs1]
ok: &#91;worker1]

TASK &#91;common : Configuramos el hostname] ******************************************
skipping: &#91;nfs1]
changed: &#91;worker1]
changed: &#91;master1]

TASK &#91;common : Actualizando los paquetes del Sistema] *****************************
changed: &#91;worker1]
changed: &#91;master1]
changed: &#91;nfs1]

TASK &#91;master_workers : Instalamos Docker] *****************************************
changed: &#91;worker1]
changed: &#91;master1]

TASK &#91;master_workers : Instalamos Kubernetes] *************************************
changed: &#91;master1]
changed: &#91;worker1]

TASK &#91;master : debug] *************************************************************
ok: &#91;master1] =&gt; {
    "msg": "Se ejecutara: kubeadm init --apiserver-advertise-address 10.0.1.21 --pod-network-cidr 10.255.0.0/16"
}

TASK &#91;master : Mostrando la salida de la ejecución del kubeadm init] **************
ok: &#91;master1] =&gt; {
    "msg": &#91;
        "&#91;init] Using Kubernetes version: v1.22.3",
        "&#91;preflight] Running pre-flight checks",
        "&#91;preflight] Pulling images required for setting up a Kubernetes cluster",
        "Your Kubernetes control-plane has initialized successfully!",
        "You should now deploy a pod network to the cluster.",
        "Run \"kubectl apply -f &#91;podnetwork].yaml\" with one of the options listed at:",
        "  https://kubernetes.io/docs/concepts/cluster-administration/addons/",
        "",
        "Then you can join any number of worker nodes by running the following on each as root:",
        "",
        "kubeadm join 10.0.1.21:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \\",
        "\t--discovery-token-ca-cert-hash sha256:fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff "
    ]
}

TASK &#91;master : Extrayendo el hash CRT CA del Master] ******************************
changed: &#91;master1]

TASK &#91;master : Extrayendo el token del Master en formato JSON] ********************
changed: &#91;master1]

TASK &#91;master : Instalamos el operador de Tigera] **********************************
changed: &#91;master1]

TASK &#91;master : Instalamos la SDN Calico] ******************************************
changed: &#91;master1]

TASK &#91;master : Mostramos el resultado de la instalación de la SDN Calico] *********
ok: &#91;master1] =&gt; {
    "msg": &#91;
        "installation.operator.tigera.io/default created"
    ]
}

TASK &#91;master : Desplegando el Nginx Ingress Controller] ***************************
changed: &#91;master1]

TASK &#91;master : Mostramos el resultado de la instalación Nginx Ingress Controller] * 
ok: &#91;master1] =&gt; {
    "msg": &#91;
        "namespace/ingress-nginx created",
        "serviceaccount/ingress-nginx created",
        "configmap/ingress-nginx-controller created",
        "clusterrole.rbac.authorization.k8s.io/ingress-nginx created",
        "clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created",
        "role.rbac.authorization.k8s.io/ingress-nginx created",
        "rolebinding.rbac.authorization.k8s.io/ingress-nginx created",
        "service/ingress-nginx-controller-admission created",
        "service/ingress-nginx-controller created",
        "deployment.apps/ingress-nginx-controller created",
        "ingressclass.networking.k8s.io/nginx created",
        "serviceaccount/ingress-nginx-admission created",
        "role.rbac.authorization.k8s.io/ingress-nginx-admission created",
        "job.batch/ingress-nginx-admission-create created",
        "job.batch/ingress-nginx-admission-patch created"
    ]
}

PLAY &#91;Realizando configuraciones en los Workers] **********************************

TASK &#91;workers : debug] ************************************************************
ok: &#91;worker1] =&gt; {
    "msg": "La IP del master es: 10.0.1.21"
}

TASK &#91;workers : Habilitamos los puertos para unirse al clúster 10250] *************
changed: &#91;worker1]

TASK &#91;workers : Habilitamos los puertos para unirse al clúster 30000 32767] *******
changed: &#91;worker1]

TASK &#91;workers : Unimos el Worker al clúster Kubernetes] ***************************
changed: &#91;worker1]

TASK &#91;workers : Resultado de la unión del Worker al clúster] **********************
ok: &#91;worker1] =&gt; {
    "msg": &#91;
        "&#91;preflight] Running pre-flight checks",
        "&#91;preflight] Reading configuration from the cluster...",
        "&#91;kubelet-start] Writing kubelet configuration to file \"/var/lib/kubelet/config.yaml\"",
        "&#91;kubelet-start] Writing kubelet environment file with flags to file \"/var/lib/kubelet/kubeadm-flags.env\"",
        "&#91;kubelet-start] Starting the kubelet",
        "&#91;kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...",
        "This node has joined the cluster:",
        "* Certificate signing request was sent to apiserver and a response was received.",
        "* The Kubelet was informed of the new secure connection details.",
        "Run 'kubectl get nodes' on the control-plane to see this node join the cluster."
    ]
}

TASK &#91;tests : Mostrando información del Cluster recién desplegado] ****************
ok: &#91;master1] =&gt; {
    "msg": "\"--&gt; SE CULMINÓ EL DESPLIEGUE DEL CLÚSTER DE KUBERNETES\"\n\"&#91;'\\x1b&#91;0;32mKubernetes control plane\\x1b&#91;0m is running at \\x1b&#91;0;33mhttps://10.0.1.21:6443\\x1b&#91;0m', '\\x1b&#91;0;32mCoreDNS\\x1b&#91;0m is running at \\x1b&#91;0;33mhttps://10.0.1.21:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy\\x1b&#91;0m', '', \"To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\"]\"\n"
}

TASK &#91;tests : Ejecutando kubectl get svc] *****************************************
changed: &#91;master1]

TASK &#91;tests : Mostramos el resultado de kubectl get svc] **************************
ok: &#91;master1] =&gt; {
"msg": &#91;
"NAMESPACE    NAME    TYPE    CLUSTER-IP    EXTERNAL-IP    PORT(S)    AGE",
"default    kubernetes    ClusterIP 10.96.0.1    &lt;none&gt;    443/TCP    45s",
"ingress-nginx  ingress-nginx-controller  NodePort  10.97.92.38  &lt;none&gt;  80:30614/TCP,443:31329/TCP  32s",
"ingress-nginx  ingress-nginx-controller-admission  ClusterIP  10.101.84.254  &lt;none&gt;  443/TCP  32s",
"kube-system  kube-dns  ClusterIP  10.96.0.10  &lt;none&gt;  53/UDP,53/TCP,9153/TCP  42s"
    ]
}</code></pre>



<p>Al finalizar la ejecución del playbook, podemos verificar si todas las tareas terminaron correctamente o si algunas fallaron:</p>



<pre class="wp-block-code has-small-font-size"><code>PLAY RECAP ************************************************************************
master1: <strong><span class="has-inline-color has-luminous-vivid-orange-color">ok=71</span></strong> changed=50 unreachable=0  <strong><span class="has-inline-color has-luminous-vivid-orange-color">failed=0</span></strong>  skipped=2  rescued=0  ignored=0
nfs1   : <strong><span class="has-inline-color has-luminous-vivid-orange-color">ok=2</span></strong>  changed=2  unreachable=0  <strong><span class="has-inline-color has-luminous-vivid-orange-color">failed=0</span></strong>  skipped=1  rescued=0  ignored=0
worker1: <strong><span class="has-inline-color has-luminous-vivid-orange-color">ok=39</span></strong> changed=28 unreachable=0  <strong><span class="has-inline-color has-luminous-vivid-orange-color">failed=0</span></strong>  skipped=0  rescued=0  ignored=0</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Instalando las Aplicaciones de ejemplo</h3>



<p>Con el clúster Kubernetes ya instalado, podemos proceder a instalar las aplicaciones de ejemplo. Para ello ejecutamos:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;localhost ansible]$ ansible-playbook -i hosts despliegue-aplicacion.yml</code></pre>



<p>Al igual que con el playbook anterior, se mostrarán una a una las tareas que se van realizando sobre el host Kubernetes master:</p>



<pre class="wp-block-code has-small-font-size"><code>PLAY &#91;Realizando el Despliegue de la aplicación sobre Kubernetes] *****************

TASK &#91;Gathering Facts] ************************************************************
ok: &#91;master1]

TASK &#91;app : Copiando los archivos de las Aplicaciones] ****************************
changed: &#91;master1] =&gt; (item=mongodb-secret.yaml)
changed: &#91;master1] =&gt; (item=mongodb-cm.yaml)
changed: &#91;master1] =&gt; (item=mongodb.yaml)
changed: &#91;master1] =&gt; (item=mongo-express.yaml)
changed: &#91;master1] =&gt; (item=ajedrez-app.yaml)
changed: &#91;master1] =&gt; (item=myingress.yaml)

TASK &#91;app : Desplegando las Aplicaciones] *****************************************
changed: &#91;master1] =&gt; (item=mongodb-secret.yaml)
changed: &#91;master1] =&gt; (item=mongodb-cm.yaml)
changed: &#91;master1] =&gt; (item=mongodb.yaml)
changed: &#91;master1] =&gt; (item=mongo-express.yaml)
changed: &#91;master1] =&gt; (item=ajedrez-app.yaml)
changed: &#91;master1] =&gt; (item=myingress.yaml)

TASK &#91;app : Mostramos el resultado del despliegue de las Aplicaciones] ************
ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/mongodb-secret.yaml', 'stdout': 'secret/mongodb-secret created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:33.595560', 'end': '2021-11-10 16:21:33.895110', 'delta': '0:00:00.299550', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb-secret.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'secret/mongodb-secret created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'mongodb-secret.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "secret/mongodb-secret created"
    ]
}

ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/mongodb-cm.yaml', 'stdout': 'configmap/mongodb-configmap created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:34.264286', 'end': '2021-11-10 16:21:34.618406', 'delta': '0:00:00.354120', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb-cm.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'configmap/mongodb-configmap created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'mongodb-cm.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "configmap/mongodb-configmap created"
    ]
}

ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/mongodb.yaml', 'stdout': 'deployment.apps/mongodb-deployment created\nservice/mongodb-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:34.943632', 'end': '2021-11-10 16:21:35.427144', 'delta': '0:00:00.483512', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'deployment.apps/mongodb-deployment created', 'service/mongodb-service created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'mongodb.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "deployment.apps/mongodb-deployment created",
        "service/mongodb-service created"
    ]
}

ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/mongo-express.yaml', 'stdout': 'deployment.apps/mongo-express created\nservice/mongo-ex-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:37.110115', 'end': '2021-11-10 16:21:40.956915', 'delta': '0:00:03.846800', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongo-express.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'deployment.apps/mongo-express created', 'service/mongo-ex-service created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'mongo-express.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "deployment.apps/mongo-express created",
        "service/mongo-ex-service created"
    ]
}

ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/ajedrez-app.yaml', 'stdout': 'deployment.apps/ajedrez-app created\nservice/ajedrez-app-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:42.890756', 'end': '2021-11-10 16:21:44.295909', 'delta': '0:00:01.405153', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/ajedrez-app.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'deployment.apps/ajedrez-app created', 'service/ajedrez-app-service created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'ajedrez-app.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "deployment.apps/ajedrez-app created",
        "service/ajedrez-app-service created"
    ]
}

ok: &#91;master1] =&gt; (item={'cmd': 'kubectl apply -f /root/myingress.yaml', 'stdout': 'ingress.networking.k8s.io/myingress created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:46.202769', 'end': '2021-11-10 16:21:49.322063', 'delta': '0:00:03.119294', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/myingress.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': &#91;'ingress.networking.k8s.io/myingress created'], 'stderr_lines': &#91;], 'failed': False, 'item': 'myingress.yaml', 'ansible_loop_var': 'item'}) =&gt; {
    "msg": &#91;
        "ingress.networking.k8s.io/myingress created"
    ]
}</code></pre>



<p>Terminada la ejecución del playbook, podemos verificar si todas las tareas terminaron satisfactoriamente o si alguna falló:</p>



<pre class="wp-block-code has-small-font-size"><code>PLAY RECAP ***********************************************************************
master1: <strong><span class="has-inline-color has-luminous-vivid-orange-color">ok=6</span></strong>  changed=3  unreachable=0  <strong><span class="has-inline-color has-luminous-vivid-orange-color">failed=0</span></strong>  skipped=0  rescued=0  ignored=0</code></pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f44c.png" alt="👌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Pruebas de estado y acceso a las Aplicaciones</h2>



<p>En esta sección verificaremos el estado del clúster Kubernetes que acabamos de instalar y el acceso a las aplicaciones de prueba.</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Verificando el estado del Clúster Kubernetes</h3>



<p>Nos conectamos al nodo master y como usuario root ejecutamos el comando <code><strong>kubectl cluster-info</strong></code>. La salida nos muestra información del clúster Kubernetes recién instalado como la URL del <em>Control Plane</em> y del <em>CoreDNS</em>:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl cluster-info
Kubernetes control plane is running at <strong><span class="has-inline-color has-luminous-vivid-orange-color">https://10.0.1.21:6443</span></strong>
CoreDNS is running at <strong><span class="has-inline-color has-luminous-vivid-orange-color">https://10.0.1.21:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy</span></strong></code></pre>



<p>Ahora ejecutaremos <strong><code>kubectl get nodes -o wide</code></strong>. Este comando nos mostrará la lista de nodos que forman parte del clúster, su estado y dirección IP:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get nodes -o wide
NAME                     STATUS  ROLES                 AGE  VERSION  INTERNAL-IP
master1.jruizcampos.com  <strong><span class="has-inline-color has-luminous-vivid-orange-color">Ready   </span></strong>control-plane,master  57m  v1.22.3  <strong><span class="has-inline-color has-luminous-vivid-orange-color">10.0.1.21</span></strong>
worker1.jruizcampos.com  <strong><span class="has-inline-color has-luminous-vivid-orange-color">Ready   </span></strong>&lt;none&gt;                57m  v1.22.3  <strong><span class="has-inline-color has-luminous-vivid-orange-color">10.0.1.51</span></strong></code></pre>



<p>Ejecutamos  <strong><code>kubectl get deployments -o wide</code></strong> y <code><strong>kubectl get pods -o wide</strong></code>. Con estos comandos verificamos si las aplicaciones de prueba que desplegamos se llegaron a inicializar de manera correcta:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get deployments -o wide
NAME                READY  UP-TO-DATE  AVAILABLE  AGE  CONTAINERS     IMAGES
ajedrez-app         <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    1           1          34m  ajedrez-app    chess
mongo-express       <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    1           1          34m  mongo-express  mongo-express
mongodb-deployment  <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    1           1          34m  mongodb        mongo</code></pre>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get pods -o wide
NAME                                READY  STATUS   RESTARTS  AGE  IP
ajedrez-app-8459955f76-699sm        <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    <strong><span class="has-inline-color has-luminous-vivid-orange-color">Running  </span></strong>0         26m  10.255.78.76
mongo-express-78fcf796b8-dgbhl      <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    <strong><span class="has-inline-color has-luminous-vivid-orange-color">Running  </span></strong>0         26m  10.255.78.75
mongodb-deployment-8f6675bc5-fc9gn  <strong><span class="has-inline-color has-luminous-vivid-orange-color">1/1</span></strong>    <strong><span class="has-inline-color has-luminous-vivid-orange-color">Running  </span></strong>0         26m  10.255.78.74</code></pre>



<p>En los resultados mostrados se verifica que todos los despliegues de aplicación se realizaron correctamente y están listos <em>(READY 1/1)</em>.</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Acceso a las aplicaciones directamente en los Nodos</h3>



<p>Las aplicaciones de prueba desplegadas son 2: Un juego de ajedrez web basado en PHP y una base de datos <a href="https://hub.docker.com/_/mongo" target="_blank" rel="noreferrer noopener nofollow">mongodb</a> con su gestor web <a href="https://github.com/mongo-express/mongo-express" target="_blank" rel="noreferrer noopener nofollow">mongo-express</a>.</p>



<p>El acceso a estas aplicaciones es a través de servicios configurados en Kubernetes como tipo <strong>NodePort</strong>. Para ver el estado de los servicios ejcutamos <strong><code>kubectl get svc</code></strong>:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get svc
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP  PORT(S)         AGE
<strong><span class="has-inline-color has-luminous-vivid-orange-color">ajedrez-app-service</span></strong> <strong><span class="has-inline-color has-luminous-vivid-orange-color">NodePort    </span></strong>10.96.76.52     &lt;none&gt;       8082:<strong><span class="has-inline-color has-luminous-vivid-orange-color">31236</span></strong>/TCP  57m
kubernetes          ClusterIP   10.96.0.1       &lt;none&gt;       443/TCP         62m
<strong><span class="has-inline-color has-luminous-vivid-orange-color">mongo-ex-service</span></strong>    <strong><span class="has-inline-color has-luminous-vivid-orange-color">NodePort    </span></strong>10.106.141.185  &lt;none&gt;       8081:<strong><span class="has-inline-color has-luminous-vivid-orange-color">32622</span></strong>/TCP  57m
mongodb-service     ClusterIP   10.103.213.238  &lt;none&gt;       27017/TCP       57m</code></pre>



<p>Al ser de tipo NodePort, para acceder a un servicio en particular debemos ingresar a la dirección ip de cualquier nodo del clúster y en el puerto devuelto por el comando anterior (resaltado en naranja).</p>



<p>Por ejemplo para acceder a la aplicación de ajedrez en el nodo master: <strong>http://10.0.1.21:31236</strong></p>



<div class="wp-block-image"><figure class="aligncenter size-full is-resized"><img decoding="async" src="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_master.jpg" alt="Juego de ajedrez en el nodo Master de Kubernetes" class="wp-image-128" width="509" height="530" srcset="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_master.jpg 678w, https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_master-288x300.jpg 288w" sizes="(max-width: 509px) 100vw, 509px" title="ajedrez kubernetes master"><figcaption>Juego de ajedrez en el nodo Master</figcaption></figure></div>



<p>Accediendo al ajedrez en el nodo worker: <strong>http://10.0.1.51:31236</strong></p>



<div class="wp-block-image"><figure class="aligncenter size-full is-resized"><img decoding="async" src="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_worker.jpg" alt="Juego de ajedrez en el nodo Worker de Kubernetes" class="wp-image-130" width="510" height="527" srcset="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_worker.jpg 680w, https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_worker-290x300.jpg 290w" sizes="(max-width: 510px) 100vw, 510px" title="ajedrez kubernetes worker"><figcaption>Juego de ajedrez en el nodo Worker</figcaption></figure></div>



<p>Ahora accederemos a la segunda aplicación, el gestor web mongo-express a través del nodo master <strong>http://10.0.1.21:32622</strong> :</p>



<div class="wp-block-image"><figure class="aligncenter size-full is-resized"><img decoding="async" src="https://johnruizcampos.com/wp-content/uploads/mongo-express-kubernetes-master.jpg" alt="Mongo Express en el nodo Master de Kubernetes" class="wp-image-131" width="684" height="461" srcset="https://johnruizcampos.com/wp-content/uploads/mongo-express-kubernetes-master.jpg 912w, https://johnruizcampos.com/wp-content/uploads/mongo-express-kubernetes-master-300x202.jpg 300w, https://johnruizcampos.com/wp-content/uploads/mongo-express-kubernetes-master-768x518.jpg 768w" sizes="(max-width: 684px) 100vw, 684px" title="mongo express kubernetes master"><figcaption>Mongo Express en el nodo Master</figcaption></figure></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f6a6.png" alt="🚦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Acceso a las aplicaciones usando un Ingress Controller</h3>



<p>La otra forma de publicar una aplicación Kubernetes es usando un Ingress Controller. En este caso, al momento de realizar el despliegue de Kubernetes se instaló el <strong><a href="https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal" target="_blank" rel="noreferrer noopener nofollow">ingress controller de Nginx</a></strong>. Podemos verificarlo ejecutando <code><strong>kubectl get svc -n ingress-nginx</strong></code> :</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get svc -n ingress-nginx
NAME                      TYPE     CLUSTER-IP    EXTERNAL-IP  PORT(S)
ingress-nginx-controller  NodePort 10.107.49.177 &lt;none&gt;  80:<strong><span class="has-inline-color has-luminous-vivid-orange-color">30575</span></strong>/TCP,443:<strong><span class="has-inline-color has-luminous-vivid-orange-color">32507</span></strong>/TCP
ingress-nginx-controller-admission  ClusterIP 10.104.4.66 &lt;none&gt;  443/TCP</code></pre>



<p>En el resultado mostrado podemos ver que el ingress controller escucha en el puerto <strong>30575 </strong>para <em>http </em>y <strong>32507 </strong>para <em>https</em>. Estos valores nos servirán más adelante.</p>



<p>Como ya contamos con un servicio de ingress controller instalado en el clúster, nos queda desplegar la configuración de ingress específica para la publicación de nuestros servicios.</p>



<p>Cuando realizamos el despliegue de las aplicaciones con el archivo <strong>despliegue-aplicacion.yml</strong>, se desplegó también la configuración de ingress especificada en <strong><a href="https://github.com/jruizcampos/kubernetes-deploy/blob/main/ansible/roles/app/files/myingress.yaml" target="_blank" rel="noreferrer noopener nofollow">myingress.yaml</a></strong>. Podemos verificarlo ejecutando <strong><code>kubectl get ingress</code></strong>:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl get ingress
NAME        CLASS    HOSTS             ADDRESS     PORTS   AGE
<strong><span class="has-inline-color has-luminous-vivid-orange-color">myingress</span></strong>   &lt;none&gt;   jruizcampos.com   10.0.1.51   80      3h35m</code></pre>



<p>Podemos revisar más a detalle esta configuración con el comando  <code><strong>kubectl describe ingress myingress</strong></code>:</p>



<pre class="wp-block-code has-small-font-size"><code>&#91;root@master1 ~]# kubectl describe ingress myingress
Name:             myingress
Namespace:        default
Address:          10.0.1.51
Default backend:  default-http-backend:80 (&lt;error: endpoints "default-http-backend" not found&gt;)
Rules:
  Host             Path  Backends
  ----             ----  --------
  <strong><span class="has-inline-color has-luminous-vivid-orange-color">jruizcampos.com</span></strong>
                   <span class="has-inline-color has-luminous-vivid-orange-color"><strong>/mongodb</strong></span>   <strong><span class="has-inline-color has-luminous-vivid-orange-color">mongo-ex-service</span></strong>:8081 (10.255.1.6:8081)
                   <strong><span class="has-inline-color has-luminous-vivid-orange-color">/ajedrez</span></strong>   <strong><span class="has-inline-color has-luminous-vivid-orange-color">ajedrez-app-service</span></strong>:8082 (10.255.1.7:80)
Annotations:       kubernetes.io/ingress.class: nginx
                   nginx.ingress.kubernetes.io/add-base-url: true
                   nginx.ingress.kubernetes.io/rewrite-target: /
Events:
  Type    Reason  Age                  From                      Message
  ----    ------  ----                 ----                      -------
  Normal  Sync    42s (x3 over 3h35m)  nginx-ingress-controller  Scheduled for sync</code></pre>



<p>La configuración nos indica que podemos acceder a la aplicación ajedrez usando el dominio <strong>jruizcampos.com</strong> y la ruta <strong>/ajedrez</strong>.</p>



<p>Adicionalmente debemos agregar a la URL de acceso el número de puerto en el que escucha el Ingress Controller, que como vimos al inicio de la sección es <strong>30575 </strong>(http) por lo que nuestra URL quedaría así: <strong>http://jruizcampos.com:30575/ajedrez</strong></p>



<div class="wp-block-image"><figure class="aligncenter size-full is-resized"><img decoding="async" src="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_nginx_ingress.jpg" alt="Accediendo al juego de Ajedrez a través del ingress controller de Nginx" class="wp-image-135" width="497" height="528" srcset="https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_nginx_ingress.jpg 663w, https://johnruizcampos.com/wp-content/uploads/ajedrez_kubernetes_nginx_ingress-283x300.jpg 283w" sizes="(max-width: 497px) 100vw, 497px" title=""><figcaption>Accediendo al juego de Ajedrez a través de un Ingress Controller</figcaption></figure></div>



<p>Para que el acceso por dominio funcione, debemos tener previamente configurado en nuestro DNS que el dominio (en este caso <strong>jruizcampos.com</strong>) apunte a la dirección ip del nodo <em>master </em>(en este caso <strong>10.0.1.21</strong>).</p>



<p>En mi caso y al ser una instalación de prueba, he realizado la configuración DNS agregando la siguiente línea al final del archivo <strong>hosts</strong> de mi equipo:</p>



<pre class="wp-block-code"><code>10.0.1.21 jruizcampos.com</code></pre>



<p>Bueno, este artículo ha quedado más extenso de lo previsto. Espero les haya sido de utilidad. Nos vemos pronto!</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
