<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://leomicheloni.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://leomicheloni.com/" rel="alternate" type="text/html" /><updated>2025-12-02T10:20:43+00:00</updated><id>https://leomicheloni.com/feed.xml</id><title type="html">Una sinfonía en C#</title><subtitle>Un humilde aporte a la comunidad de habla hispana.</subtitle><author><name>Leonardo Micheloni</name></author><entry><title type="html">¿Cómo conectar Kakfa-UI con Event Hubs de Azure?</title><link href="https://leomicheloni.com/Kafka-UI-Event-hubs/" rel="alternate" type="text/html" title="¿Cómo conectar Kakfa-UI con Event Hubs de Azure?" /><published>2025-12-02T00:00:00+00:00</published><updated>2025-12-02T00:00:00+00:00</updated><id>https://leomicheloni.com/Kafka-UI-Event-hubs</id><content type="html" xml:base="https://leomicheloni.com/Kafka-UI-Event-hubs/"><![CDATA[<h2 id="introducción">“Introducción”</h2>

<p>Si usamos Eventhubs de Azure como broker Kafka, y queremos usar Kafka-UI para monitorizar los topics, tenemos que tener en cuenta algunas cosas para que funcione.
Hablando siempre de autenticación utilizando SASL_SSL con mecanismo PLAIN. (Esto no funciona para Managed Identity).</p>

<p>En principio sabemos que podemos definir un connection string SAS para conectarnos a Event Hubs, pero Kafka-UI no soporta directamente este formato, por lo que tenemos que hacer algunos ajustes.</p>

<blockquote>
  <p>KAfka en generar no soporta este formado, sino que hay que hacer un mapeo de los valores del connection string a las propiedades que Kafka espera, más específicamente en el usuario y password, además de protocolo de comunicación.</p>
</blockquote>

<h2 id="configuración-de-client-kafka">Configuración de Client Kafka</h2>

<p>Para conectar un cliente Kafka a Event Hubs, tenemos que mapear los valores del connection string a las propiedades de usuario y password.
Pero primero hay que configurar:</p>

<ul>
  <li>Protocol: SASL_SSL</li>
  <li>Mechanism: PLAIN</li>
</ul>

<p>Y luego en las propiedades de autenticación:</p>
<ul>
  <li>Username: $ConnectionString</li>
  <li>Password: Endpoint=sb://<NAMESPACE>.servicebus.windows.net/;SharedAccessKeyName=<KEY_NAME>;SharedAccessKey=<KEY_VALUE>;EntityPath=<EVENT_HUB_NAME></EVENT_HUB_NAME></KEY_VALUE></KEY_NAME></NAMESPACE></li>
</ul>

<blockquote>
  <p>El EntityPath es opcional, y solo si queremos conectarnos a un Event Hub específico. Si no se especifica, se puede acceder a todos los Event Hubs dentro del namespace.</p>
</blockquote>

<p>Básicamente el username es el literal <code class="language-plaintext highlighter-rouge">$ConnectionString</code> (con el signo $) y el password es el connection string completo.</p>

<h2 id="conectar-kafka-ui">Conectar Kafka-UI</h2>

<p>En el caso de Kafka-UI tenemos un truco adicional, que sería agregar al user name un $ adicional al inicio, quedando así:</p>
<ul>
  <li>Username: $$ConnectionString</li>
  <li>Password: Endpoint=sb://<NAMESPACE>.servicebus.windows.net/;SharedAccess</NAMESPACE></li>
</ul>

<p>Esto en la propiedad <strong>KAFKA_CLUSTERS_0_PROPERTIES_SASL_JAAS_CONFIG</strong> (reemplazar el 0 por el índice del cluster que estemos configurando).</p>

<p>En el caso de un docker-compose.yml, quedaría algo así:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">kafka-ui</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">provectuslabs/kafka-ui:latest</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">9999:8080</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">KAFKA_CLUSTERS_0_NAME=azure</span>
      <span class="pi">-</span> <span class="s">KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=xxxx.servicebus.windows.net:9093</span>
      <span class="pi">-</span> <span class="s">KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL=SASL_SSL</span>
      <span class="pi">-</span> <span class="s">KAFKA_CLUSTERS_0_PROPERTIES_SASL_MECHANISM=PLAIN</span>
      <span class="pi">-</span> <span class="s">KAFKA_CLUSTERS_0_PROPERTIES_SASL_JAAS_CONFIG=org.apache.kafka.common.security.plain.PlainLoginModule required username='$$ConnectionString' password='Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;KEY_NAME&gt;;SharedAccessKey=&lt;KEY_VALUE&gt;';</span>
</code></pre></div></div>
<p>El connection string tiene que tener scope del namespace (lo que sería el broker de Kafka).</p>

<p><strong>Especial cuidado al doble $ en el username y al ; final</strong></p>

<p>Nos leemos.</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[“Introducción”]]></summary></entry><entry><title type="html">Docker tricks, crear una imagen para poder depurar un error.</title><link href="https://leomicheloni.com/troubleshooting-docker/" rel="alternate" type="text/html" title="Docker tricks, crear una imagen para poder depurar un error." /><published>2024-09-10T00:00:00+00:00</published><updated>2024-09-10T00:00:00+00:00</updated><id>https://leomicheloni.com/troubleshooting-docker</id><content type="html" xml:base="https://leomicheloni.com/troubleshooting-docker/"><![CDATA[<h2 id="introducción">“Introducción”</h2>

<p>En este caso queremos crear una imagen pero nos da algún tipo de error, y es complicado de resolver.
Bueno, lo que podemos hacer es apuntar los comandos que queremos ejecutar, crear una imagen con su base y hasta el punto que funciona y hacer que inicie con un comando que nos permita crear al contenedor e ingresar.</p>

<h2 id="crear-imagen-a-partir-de-una-con-problemas">Crear imagen a partir de una con problemas</h2>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">node:20.12.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span>
<span class="k">ARG</span><span class="s"> environment=dev</span>

<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> ./package.json /app/</span>
<span class="k">COPY</span><span class="s"> ./yarn.lock /app/</span>

<span class="k">RUN </span>yarn
<span class="k">COPY</span><span class="s"> . /app/</span>
<span class="k">RUN </span>yarn build:<span class="nv">$environment</span>

<span class="k">FROM</span><span class="s"> nginx:1.21.5-alpine</span>

<span class="k">EXPOSE</span><span class="s"> 80/tcp</span>
<span class="k">COPY</span><span class="s"> nginx.conf /etc/nginx/nginx.conf</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/dist /usr/share/nginx/html</span>
<span class="k">CMD</span><span class="s"> ["nginx", "-g", "daemon off;"]</span>
</code></pre></div></div>

<p>y vemos que nos da un error al hacer <code class="language-plaintext highlighter-rouge">RUN yarn</code></p>

<h2 id="qué-podemos-hacer">¿Qué podemos hacer?</h2>

<p>Facil, creamos una imagen con la base que tenemos y hasta el punto que funciona, y luego la ejecutamos con un comando que nos permita ingresar al contenedor.
Pero como comando de inicio, usamos <code class="language-plaintext highlighter-rouge">tail -f /dev/null</code> para que se quede esperando y no se cierre.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">node:20.12.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span>
<span class="k">ARG</span><span class="s"> environment=dev</span>

<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> ./package.json /app/</span>
<span class="k">COPY</span><span class="s"> ./yarn.lock /app/</span>

<span class="k">CMD</span><span class="s"> ["tail", "-f", "/dev/null"]</span>
</code></pre></div></div>

<p>una vez hecho esto, podemos hacer un <code class="language-plaintext highlighter-rouge">docker build -t myimage .</code> y luego un <code class="language-plaintext highlighter-rouge">docker run -it myimage /bin/bash</code> para ingresar al contenedor y ver que es lo que pasa.</p>

<p>Desde dentro del container ejecutamos el comando que da problemas y vemos el error que nos da.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn

.....

Request failed <span class="se">\"</span>401 Unauthorized<span class="se">\"</span><span class="s2">"

</span></code></pre></div></div>

<p>Y vemos que nos da un error al intentar restaurar los paquetes.</p>

<p>Nada más, una forma sencilla de ir depurando error por error dentro de un contenedor.</p>

<p>Agregamos una línea para copiar el archivo de configuración de npm y listo.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">node:20.12.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span>
<span class="k">ARG</span><span class="s"> environment=dev</span>

<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> ./package.json /app/</span>
<span class="k">COPY</span><span class="s"> ./yarn.lock /app/</span>
<span class="k">COPY</span><span class="s"> .npmrc /app/ # &lt;-- Agregamos esta línea</span>

<span class="k">RUN </span>yarn
<span class="k">COPY</span><span class="s"> . /app/</span>
<span class="k">RUN </span>yarn build:<span class="nv">$environment</span>

<span class="k">FROM</span><span class="s"> nginx:1.21.5-alpine</span>

<span class="k">EXPOSE</span><span class="s"> 80/tcp</span>
<span class="k">COPY</span><span class="s"> nginx.conf /etc/nginx/nginx.conf</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/dist /usr/share/nginx/html</span>
<span class="k">CMD</span><span class="s"> ["nginx", "-g", "daemon off;"]</span>
</code></pre></div></div>

<p>Nos leemos.</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[“Introducción”]]></summary></entry><entry><title type="html">Configurar Docker + HTTPS + nginx</title><link href="https://leomicheloni.com/https/" rel="alternate" type="text/html" title="Configurar Docker + HTTPS + nginx" /><published>2024-05-21T00:00:00+00:00</published><updated>2024-05-21T00:00:00+00:00</updated><id>https://leomicheloni.com/https</id><content type="html" xml:base="https://leomicheloni.com/https/"><![CDATA[<h2 id="introducción">“Introducción”</h2>

<p>Cuando queremos probar algo en local (una aplicación web), dentro de un contenedor, no podemos utilizar https como desde Visual Studio o IIS.
Si necesitamos sí o sí https, debemos configurar algunas cosas, como por ejemplo, un certificado autofirmado, nginx, etc.
En este post vamos a detaler cómo hacerlo.</p>

<p><img src="../images/nginxhttps.png" alt="" /></p>

<h2 id="pasos">Pasos</h2>

<p>Vamos a necesitar hacer un par de cosas, voy a detallar los pasos a seguir en una PC con Windows y una aplicación .NET Core, es que lo que yo uso.</p>

<p>Básicamente pondremos nuestra aplicación en un contenedor, configuraremos nginx para que haga de proxy y que además tenga https. Luego un docker compose que levante todo.</p>

<h2 id="crear-certificado-autofirmado">Crear certificado autofirmado</h2>

<p>Para crear certificados autofirmados, podemos utilizar openssl. En Windows, podemos instalarlo desde <a href="https://slproweb.com/products/Win32OpenSSL.html">aquí</a>.</p>

<p>y ejecutar este comando:</p>

<p><strong>localhost.conf</strong></p>

<pre><code class="language-txt">[req]
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

[req_distinguished_name]
countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = Texas
localityName                = Locality Name (eg, city)
localityName_default        = Dallas
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

[req_ext]
subjectAltName = @alt_names

[v3_ca]
subjectAltName = @alt_names

[alt_names]
DNS.1   = localhost
DNS.2   = 127.0.0.1
</code></pre>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">openssl</span><span class="w"> </span><span class="nx">req</span><span class="w"> </span><span class="nt">-x509</span><span class="w"> </span><span class="nt">-nodes</span><span class="w"> </span><span class="nt">-days</span><span class="w"> </span><span class="nx">365</span><span class="w"> </span><span class="nt">-newkey</span><span class="w"> </span><span class="nx">rsa:2048</span><span class="w"> </span><span class="nt">-keyout</span><span class="w"> </span><span class="nx">localhost.key</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="nx">localhost.crt</span><span class="w"> </span><span class="nt">-config</span><span class="w"> </span><span class="nx">localhost.conf</span><span class="w"> </span><span class="nt">-passin</span><span class="w"> </span><span class="nx">pass:12345678</span><span class="w">
</span></code></pre></div></div>
<p>Esto creará dos archivos, localhost.crt y localhost.key, estos archivos los usuaremos en nginx.</p>

<p>Ahora necesitamos registrar el certificado como confiable ya que es auto-firmado. (es decir, registrar en Windows como confiable)</p>

<p>En el Administrador de Certificados (certmgr.msc), puedes encontrar esta ubicación siguiendo estos pasos:</p>

<p>Abrimos el Administrador de Certificados.</p>
<ul>
  <li>Para esto escribimos certmgr.msc en el diálogo Ejecutar (Win + R).</li>
  <li>En el Administrador de Certificados, expande el árbol de Certificados “Usuario Actual” en el panel izquierdo.</li>
  <li>Debajo de esto, expande la carpeta Autoridades de Certificación Raíz Confiables.</li>
</ul>

<p>Hacemos clic en la carpeta Certificados bajo Autoridades de Certificación Raíz Confiables.</p>

<p>Esta es la ubicación equivalente a Cert:\CurrentUser\Root en PowerShell.</p>

<p>Luego</p>

<p>En el Administrador de Certificados (certlm.msc, Certificate Manager for local machine), puedes encontrar esta ubicación siguiendo estos pasos:</p>

<ul>
  <li>Abre el Administrador de Certificados para la Máquina Local. Puedes hacer esto escribiendo certlm.msc en el diálogo Ejecutar (Win + R).</li>
  <li>En el Administrador de Certificados, expande el árbol Certificados (Computadora Local) en el panel izquierdo.</li>
  <li>Debajo de esto, expande la carpeta Personal.</li>
  <li>Haz clic en la carpeta Certificados bajo Personal.</li>
</ul>

<p>Ahora nginx usará los archivos de certificado y clave para servir https. Y deberías estar bien.</p>

<h2 id="configurar-nginx">Configurar nginx</h2>

<p>Para esto simplemente vamos a crear un archivo de configuración para nginx, que será el siguiente:</p>

<p><strong>nginx.conf</strong></p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">worker_processes</span> <span class="mi">1</span><span class="p">;</span>

<span class="k">events</span> <span class="p">{</span> <span class="kn">worker_connections</span> <span class="mi">1024</span><span class="p">;</span> <span class="p">}</span>

<span class="k">http</span> <span class="p">{</span>

    <span class="kn">sendfile</span> <span class="no">on</span><span class="p">;</span>

    <span class="kn">upstream</span> <span class="s">web-api</span> <span class="p">{</span>
        <span class="kn">server</span> <span class="nf">api</span><span class="p">:</span><span class="mi">80</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kn">server</span> <span class="p">{</span>
        <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
        <span class="kn">server_name</span> <span class="s">localhost</span><span class="p">;</span>

        <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
            <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kn">server</span> <span class="p">{</span>
        <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
        <span class="kn">server_name</span> <span class="s">localhost</span><span class="p">;</span>

        <span class="kn">ssl_certificate</span> <span class="n">/etc/ssl/certs/localhost.crt</span><span class="p">;</span>
        <span class="kn">ssl_certificate_key</span> <span class="n">/etc/ssl/private/localhost.key</span><span class="p">;</span>

        <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
            <span class="kn">proxy_pass</span>         <span class="s">http://web-api</span><span class="p">;</span>
            <span class="kn">proxy_redirect</span>     <span class="no">off</span><span class="p">;</span>
            <span class="kn">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
            <span class="kn">proxy_cache_bypass</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">Upgrade</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">Connection</span> <span class="s">keep-alive</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
            <span class="kn">proxy_set_header</span>   <span class="s">X-Forwarded-Host</span> <span class="nv">$server_name</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Le decimos a nginx que utilice el certificado y la clave que creamos antes, y que escuche en el puerto 443.
y con el proxy_pass le decimos que redirija las peticiones al servicio que escucha en el puerto 80. (más adelante ese será el nombre del servicio en el docker compose)</p>

<p>Ahora creamos el Dockerfile para nginx:</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> nginx:alpine</span>

<span class="k">COPY</span><span class="s"> ./nginx.conf /etc/nginx/nginx.conf</span>
<span class="k">COPY</span><span class="s"> localhost.crt /etc/ssl/certs/localhost.crt</span>
<span class="k">COPY</span><span class="s"> localhost.key /etc/ssl/private/localhost.key</span>

<span class="k">CMD</span><span class="s"> ["nginx", "-g", "daemon off;"]</span>
</code></pre></div></div>
<p>Básicamente copiamos el archivo de configuración y los archivos de certificado y clave al contenedor.</p>

<h2 id="configurar-nuestra-app">Configurar nuestra app</h2>
<p>En este caso una simple aplicación .NET Core, que escucha en el puerto 80.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:8.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>

<span class="k">COPY</span><span class="s"> *.csproj ./</span>
<span class="k">RUN </span>dotnet restore

<span class="k">COPY</span><span class="s"> . ./</span>
<span class="k">RUN </span>dotnet publish <span class="nt">-c</span> Release <span class="nt">-o</span> out

<span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/aspnet:8.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">runtime</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>

<span class="k">ENV</span><span class="s"> ASPNETCORE_HTTP_PORTS 80</span>

<span class="k">EXPOSE</span><span class="s"> 80</span>
<span class="k">EXPOSE</span><span class="s"> 443</span>

<span class="k">COPY</span><span class="s"> --from=build /app/out ./</span>

<span class="k">CMD</span><span class="s"> ["dotnet", "app.dll"]</span>
</code></pre></div></div>

<h2 id="docker-compose">Docker compose</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.7"</span>

<span class="na">services</span><span class="pi">:</span>

<span class="na">reverseproxy</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">./nginx</span>
      <span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile.nginx</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8080:80"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">1443:443"</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>

<span class="na">api</span><span class="pi">:</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">reverseproxy</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
      <span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8088:80"</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
</code></pre></div></div>

<p>Simplemente apuntamos los dos servicios a sus Dockerfiles, y abrimos el puerto 1443 para https.</p>

<p>Si vemos algún error en el navegador relacionado con https lo más probable es que no hayamos registrado el certificado como confiable.</p>

<p><a href="https://github.com/leomicheloni/nginx-https-docker-netcore">Dejo por acá un repositorio con el código</a></p>

<p>Nos leemos.</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[“Introducción”]]></summary></entry><entry><title type="html">Cómo crear una imágen de Docker sin tener Docker instalado gracias a Azure</title><link href="https://leomicheloni.com/build-docker-image-with-azure/" rel="alternate" type="text/html" title="Cómo crear una imágen de Docker sin tener Docker instalado gracias a Azure" /><published>2024-01-16T00:00:00+00:00</published><updated>2024-01-16T00:00:00+00:00</updated><id>https://leomicheloni.com/build-docker-image-with-azure</id><content type="html" xml:base="https://leomicheloni.com/build-docker-image-with-azure/"><![CDATA[<h1 id="introducción">Introducción</h1>

<p>Todos nos hemos encontrado en la encrucijada de tener que crear una imagen de Docker y no tener Docker instalado en nuestro equipo. En este post vamos a ver cómo podemos crear una imagen de Docker sin tener Docker instalado en nuestro equipo utilizando una cuenta de Azure.</p>

<h2 id="el-problema">El problema</h2>
<p>Primero necesitamos una cuenta de Azure y una instancia de Azure Container Registry a la que tengamos acceso.
La idea es que teniendo el código o el binario, lo que queramos meter en la imagen (Sea lo que sea, multistae o no) y el Dockerfile podemos utilizar la capacidad de Azure Container Registry de crear una imagen a partir de un Dockerfile.</p>

<blockquote>
  <p>Importante, nuestro código o binario subirán a la nube durante el proceso</p>
</blockquote>

<p>Entonces, teniendo ya el código o binario y el Dockerfile, vamos a crear la imagen.</p>

<h2 id="pasos-para-crear-la-imagen-y-subirla-al-acr">Pasos para crear la imagen y subirla al ACR</h2>

<p>Antes de nada tenemos que tener (además de la cuenta de Azure y el ACR) instalado el Azure CLI. Lo podemos descargar desde <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest">aquí</a></p>

<ul>
  <li>Hacer login en Azure</li>
  <li>Seleccionar la suscripción donde tenemos el ACR</li>
  <li>Crear la imagen</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="n">az</span><span class="w"> </span><span class="nx">login</span><span class="w">

</span><span class="n">az</span><span class="w"> </span><span class="nx">account</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="nt">--subscription</span><span class="w"> </span><span class="nx">KKKKKK-KKKK-KKKK-KKKK-KKKKKKKKK</span><span class="w">

</span><span class="n">az</span><span class="w"> </span><span class="nx">acr</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="nt">--registry</span><span class="w"> </span><span class="nx">miurl.azurecr.io</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="nx">Dockerfile</span><span class="w"> </span><span class="nt">--image</span><span class="w"> </span><span class="nx">test:latest</span><span class="w"> </span><span class="o">.</span><span class="w">

</span></code></pre></div></div>
<p><strong>como vemos el comando acr build es idéntico al docker build, pero en vez de utilizar el contexto local, lo hace en la nube.</strong></p>

<p>Y como dije antes, tomará un tiempo porque subirá el código o binario a la nube y luego creará la imagen.</p>

<p><img src="../images/acr.png" alt="" /></p>

<p>Una vez que termine, podemos ver la imagen en el ACR.</p>

<p>Enjoy!</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[Introducción]]></summary></entry><entry><title type="html">Cómo enviar datos desde .NET a Prometheus</title><link href="https://leomicheloni.com/como-enviar-datos-prometheus-dotnet.md/" rel="alternate" type="text/html" title="Cómo enviar datos desde .NET a Prometheus" /><published>2023-09-04T00:00:00+00:00</published><updated>2023-09-04T00:00:00+00:00</updated><id>https://leomicheloni.com/como-enviar-datos-prometheus-dotnet.md</id><content type="html" xml:base="https://leomicheloni.com/como-enviar-datos-prometheus-dotnet.md/"><![CDATA[<h1 id="introducción">Introducción</h1>

<p>Prometheus es un sistema de monitoreo de código abierto que nos permite almacenar series de tiempo de datos numéricos. Es muy utilizado para monitorear aplicaciones y servicios en producción.
En este caso vamos a ver cómo enviar datos desde una aplicación .NET a Prometheus.</p>

<h2 id="agregar-el-paquete-nuget">Agregar el paquete NuGet</h2>

<p>Utilizaremos https://github.com/prometheus-net/prometheus-net en nuestra aplicación .NET.</p>

<p>Y agregaremos el siguiete código</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">internal</span> <span class="k">class</span> <span class="nc">Program</span>
    <span class="p">{</span>
        <span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
        <span class="p">{</span>

            <span class="k">using</span> <span class="nn">var</span> <span class="n">server</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">KestrelMetricServer</span><span class="p">(</span><span class="n">port</span><span class="p">:</span> <span class="m">1234</span><span class="p">);</span>
            <span class="n">server</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>

            <span class="c1">// Generate some sample data from fake business logic.</span>
            <span class="kt">var</span> <span class="n">recordsProcessed</span> <span class="p">=</span> <span class="n">Metrics</span><span class="p">.</span><span class="nf">CreateCounter</span><span class="p">(</span><span class="s">"sample_records_processed_total"</span><span class="p">,</span> <span class="s">"Total number of records processed."</span><span class="p">);</span>


            <span class="n">_</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="k">async</span> <span class="k">delegate</span>
            <span class="p">{</span>
                <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="c1">// Pretend to process a record approximately every second, just for changing sample data.</span>
                    <span class="n">recordsProcessed</span><span class="p">.</span><span class="nf">Inc</span><span class="p">();</span>

                    <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">1</span><span class="p">));</span>
                <span class="p">}</span>
            <span class="p">});</span>

            <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Listening on port 1234"</span><span class="p">);</span>
            <span class="n">Console</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>Con este tenemos por un lado las métricas por defecto que nos da Prometheus y por otro lado una métrica que nosotros creamos llamada <code class="language-plaintext highlighter-rouge">sample_records_processed_total</code> que es un contador que incrementa cada segundo.</p>

<h2 id="configurar-prometheus-para-que-lea-nuestros-datos">Configurar Prometheus para que lea nuestros datos</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">global</span><span class="pi">:</span>
  <span class="na">scrape_interval</span><span class="pi">:</span> <span class="s">5s</span>
  <span class="na">evaluation_interval</span><span class="pi">:</span> <span class="s">5s</span>

<span class="na">scrape_configs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">my_net_application'</span>
  <span class="na">static_configs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">host.docker.internal:1234'</span><span class="pi">]</span>

</code></pre></div></div>

<p>Llamaremos a este archivo <code class="language-plaintext highlighter-rouge">prometheus.yml</code> y se copiará en la carpeta donde Prometheus busca su configuración.</p>

<h2 id="ejecutar-prometheus-desde-docker">Ejecutar Prometheus desde Docker</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.7"</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">prometheus</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">prom/prometheus</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">prometheus</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./prometheus.yml:/etc/prometheus/prometheus.yml</span>
    <span class="na">command</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">--config.file=/etc/prometheus/prometheus.yml'</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">9090:9090</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
</code></pre></div></div>

<p>En el comando <code class="language-plaintext highlighter-rouge">command</code> le indicamos a Prometheus que lea la configuración desde el archivo <code class="language-plaintext highlighter-rouge">prometheus.yml</code> que montamos en el volumen, éste es el archivo que creamos en el paso anterior.</p>

<h2 id="ejecutar-nuestra-aplicación-net">Ejecutar nuestra aplicación .NET</h2>

<p>Ejecutamos la aplicación y vemos que Prometheus está leyendo los datos que enviamos desde nuestra aplicación.</p>

<p><img src="../images/prometheus.png" alt="" /></p>

<p>Dejo el código de ejemplo por acá https://github.com/leomicheloni/prometheus-net-sample</p>

<p>Enjoy.</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[Introducción]]></summary></entry><entry><title type="html">Cómo recuperar un branch git en Azure DevOps</title><link href="https://leomicheloni.com/restore-deleted-branch-ado/" rel="alternate" type="text/html" title="Cómo recuperar un branch git en Azure DevOps" /><published>2023-08-22T00:00:00+00:00</published><updated>2023-08-22T00:00:00+00:00</updated><id>https://leomicheloni.com/restore-deleted-branch-ado</id><content type="html" xml:base="https://leomicheloni.com/restore-deleted-branch-ado/"><![CDATA[<h1 id="introducción">Introducción</h1>

<p>En el post anterior vimos cómo recuperar un repositorio Git que ha sido borrado en Azure Devops. En este caso vamos a ver cómo recuperar un branch que ha sido borrado.</p>

<h2 id="el-método-fácil">El método fácil</h2>

<p>Existe ya un método dentro del portal de Azure DevOps para recuperar un branch borrado. Para ello, vamos a la pestaña Repos y seleccionamos el repositorio donde estaba el branch.
Si recordamos el nombre del branch borrado solo tenemos que buscar el branch del siguiente modo:</p>

<p><img src="../images/restore_branch_1.png" alt="" /></p>

<p>Buscamos el branch por nombre y vemos que aparece debajo en la lista de branches borrados, haciendo click en los puntos de la derecha podemos restaurarlo mediante la opción Restore.
Y voilá, ya tenemos nuestro branch de vuelta.</p>

<h2 id="no-sabemos-el-nombre-del-branch-que-fue-borrado">No sabemos el nombre del branch que fue borrado</h2>

<p>En el caso que no recordemos exactamente el nombre no podemos buscarlo, pero podemos mediante git obtener el listado de branches borrados y restaurar el que queramos.</p>

<blockquote>
  <p>Disclaimer: Este método solo funciona si hicimos checkout del branch en el repo que queremos recuperar el nombre.</p>
</blockquote>

<p>Para ello, vamos a nuestro repositorio local y ejecutamos el siguiente comando:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
git reflog

</code></pre></div></div>

<p>Esto nos va a mostrar el listado de commits que hemos hecho en nuestro repositorio local, incluyendo los que hemos hecho en branches que ya no existen.</p>

<p>Con esto vamos a Azure DevOps y buscamos el branch por nombre y lo veremos para recuperarlo igual que en el método anterior.</p>

<p>Enjoy.</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[Introducción]]></summary></entry><entry><title type="html">Cómo recuperar repositorios git en Azure DevOps</title><link href="https://leomicheloni.com/restore-deleted-repo-ado/" rel="alternate" type="text/html" title="Cómo recuperar repositorios git en Azure DevOps" /><published>2023-08-08T00:00:00+00:00</published><updated>2023-08-08T00:00:00+00:00</updated><id>https://leomicheloni.com/restore-deleted-repo-ado</id><content type="html" xml:base="https://leomicheloni.com/restore-deleted-repo-ado/"><![CDATA[<blockquote>
  <p>Aclaración, este método solo funciona durante 30 días después del borrado.</p>
</blockquote>

<h1 id="introducción">Introducción</h1>
<p>Bien, en este post vamos a ver cómo recuperar un repositorio git que hemos borrado en Azure DevOps. Estos repositorios se mantienen en un Recycle Bin durante 30 días, pasado ese tiempo, se borran definitivamente.</p>

<h1 id="recuperar-repositorio">Recuperar repositorio</h1>
<p>Para hacerlo tendremos que utilizar la API de Azure DevOps. Para ello, vamos a utilizar Postman.</p>

<h2 id="obtener-token">Obtener token</h2>
<p>Lo primero que tenemos que hacer es obtener un token para poder acceder a la API. Para ellos vamos a ir a nuestro perfil y crear un token (PAT).</p>

<p>Con permisos para lo que necesitamos, en este caso, repositorios.</p>

<p><img src="../images/menu_pat.png" alt="" /></p>

<p><img src="../images/select_pat.png" alt="" /></p>

<p>Copiamos el token y lo guardamos en un lugar seguro (si lo olvidamos, tendremos que crear otro).</p>

<h2 id="consultar-repositorios-usando-la-api">Consultar repositorios usando la API</h2>

<p>Ahora vamos a consultar los repositorios que tenemos en nuestra organización. Para ello, vamos a utilizar la siguiente URL:</p>

<pre><code class="language-url">//_apis/git/repositories/?api-version=7.0
</code></pre>

<p>En mi caso es:</p>

<pre><code class="language-url">https://dev.azure.com/leomicheloni/test_git/_apis/git/repositories/?api-version=7.0
</code></pre>
<p>y usaremos basic auth con nuestro usuario y el token que acabamos de crear.
Para ellos tenemos que ir a la pestaña Authorization y seleccionar Basic Auth, o crear el header manualmente con el usuario y el token.
Por ejemplo:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usuario@email.com:PAT

</code></pre></div></div>
<p>En base 64 y lo ponemos como header anteponiendo la palabra Basic.</p>

<p>Authorization: Basic dXNlcm5hbWVAZW1haWwuY29tOlBBVA==</p>

<p>Bien, con esto obtenemos los repositorios actuales.</p>

<p><img src="../images/get_repo_response.png" alt="" /></p>

<h2 id="consultar-repositorios-borrados">Consultar repositorios borrados</h2>

<p>En este caso la url es:</p>

<pre><code class="language-url">//_apis/git/recycleBin/repositories/?api-version=7.0
</code></pre>

<p>Y el resto es igual que antes. En este caso no hay nada.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"count"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Así que borramos un repositorio y volvemos a consultar.</p>

<p><img src="../images/deleted_repo.png" alt="" /></p>

<h2 id="recuperar-repositorio-1">Recuperar repositorio</h2>

<p>Lo que tenemos que hacer es copiar el id del repositorio borrado y hacer estaba vez un PATH.</p>

<pre><code class="language-url">//_apis/git/recycleBin/repositories/[REPO_ID]?api-version=7.0
</code></pre>

<p>En mi caso:</p>

<pre><code class="language-url">https://dev.azure.com/leomicheloni/test_git/_apis/git/recycleBin/repositories/a852e4c3-7ebc-4915-ae2a-e673d79b60eb?api-version=7.0
</code></pre>

<p>Adcionalmente tenemos que indicar content-type: application/json y colocar en el body:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"deleted"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Ejecutamos el PATCH con la misma autenticación y voilá, ya tenemos el repositorio de vuelta.</p>

<p>Enjoy!</p>]]></content><author><name>Leonardo Micheloni</name></author><summary type="html"><![CDATA[Aclaración, este método solo funciona durante 30 días después del borrado.]]></summary></entry><entry><title type="html">Parámetros, entrypoints y comandos en Kubernetes, ¿cómo funcionan?</title><link href="https://leomicheloni.com/k8s-cmd-args/" rel="alternate" type="text/html" title="Parámetros, entrypoints y comandos en Kubernetes, ¿cómo funcionan?" /><published>2022-03-05T00:00:00+00:00</published><updated>2022-03-05T00:00:00+00:00</updated><id>https://leomicheloni.com/k8s-cmd-args</id><content type="html" xml:base="https://leomicheloni.com/k8s-cmd-args/"><![CDATA[<p>Si estamos desplegando en <strong>Kubernetes</strong> en probable que si algo sale mal y necesitamos hacer troubleshooting, entre las herramientas más útiles (quitando los logs) poder ejecutar un comando o “entrar” en un container puede ser muy útil. El problema es si nuestro ejecutable es el que hace que el Pod se reinicie y no podemos obtener acceso al bash del Pod, una forma de solucionar eso es sobre-escribir el comando por defecto, vamos a hablar de eso.</p>

<h1 id="docker-exec-kubectl-exec">Docker exec, Kubectl exec</h1>
<p>La forma de ejecutar un comando (y obtener acceso a la línea de comandos) contra un container esté o no en Kubernetes es utilizando <strong>exec</strong> (varia un poco la sintáxis entre ambos pero es igual) algo así:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">exec</span><span class="w"> </span><span class="nt">-it</span><span class="w"> </span><span class="nx">mycontainer</span><span class="w"> </span><span class="nx">bash</span><span class="w">
</span></code></pre></div></div>

<p>o en Kubernetes</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kubectl</span><span class="w"> </span><span class="nx">exec</span><span class="w"> </span><span class="nt">-it</span><span class="w"> </span><span class="nx">mypod</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="nx">bash</span><span class="w">
</span></code></pre></div></div>

<p>Y con esto obtener acceso a la línea de comandos ya que ejecutamos <strong>bash</strong> y solicitamos modo interactivo con <strong>-it</strong></p>

<h1 id="nuestro-pod-no-para-de-reiniciarse">Nuestro Pod no para de reiniciarse</h1>
<p>El problema es que muchas veces nuestra aplicación no terminar de iniciar y genera que el Pod se reinicie todo el tiempo, entonces no podemos acceder a la línea de comando, entonces lo que podemos hacer el sobre-escribir el comando que ejecuta nuestra app y así obtener acceso a la línea de comando y ejecutarla desde ahí y hacer las pruebas correspondientes</p>

<h1 id="entrypoint-cmd-args-commands">Entrypoint, CMD, args, Commands.</h1>

<p>Acá comienza la confusión</p>

<h4 id="si-creamos-una-imagen-de-algo-que-no-tiene-un-comando">Si creamos una imagen de algo que no tiene un comando</h4>
<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
</code></pre></div></div>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="nt">-t</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="o">.</span><span class="w">
</span></code></pre></div></div>

<h4 id="y-creamos-un-contenedor">Y creamos un contenedor</h4>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nt">-d</span><span class="w"> </span><span class="nx">test</span><span class="w">
</span></code></pre></div></div>
<h4 id="el-mismo-se-ejecuta-y-finaliza-inmeditamente-porque-la-idea-es-que-sea-un-proceso-continuo-por-eso-necesitamos-un-comando">el mismo se ejecuta y finaliza inmeditamente porque la idea es que sea un proceso continuo, por eso necesitamos un comando</h4>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">CMD</span><span class="s"> ["tail", "-f", "/dev/null"]</span>
</code></pre></div></div>

<h4 id="o-un-entrypoint">o un Entrypoint</h4>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["tail", "-f", "/dev/null"]</span>
</code></pre></div></div>
<h4 id="pero-cuál-es-la-diferencia-entre-ambos">¿Pero cuál es la diferencia entre ambos?</h4>
<p>En principio ninguna en este caso, pero si lo que queremos es dar la posiblidad de que nuestro comando reciba parámetros (es decir algo detrás del nombre del ejecutable o script, en este caso “f” “/dev/null”) ahí sí que hay diferencia:</p>

<h4 id="cómo-funciona-el-pasaje-de-parámetros-en-docker">Cómo funciona el pasaje de parámetros en Docker?</h4>
<p>Al final todo se resume a cómo funciona en Docker ya que Kubernetes se basa en ese comportamiento.
Para no ser largo con ejemplos:</p>

<h4 id="lo-que-se-defina-con-cmd-se-sobre-escribe-por-completo-con-parámetros-en-la-línea-de-comandos-entonces-esto">Lo que se defina con CMD se sobre-escribe por completo con parámetros en la línea de comandos, entonces esto</h4>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">CMD</span><span class="s"> ["echo", "hola"]</span>
</code></pre></div></div>

<p>Si lo ejecuto imprimer “hola” y finaliza. Pero es completamente sobre-escrito si hago esto:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="nx">echo</span><span class="w"> </span><span class="nx">chau</span><span class="w">
</span></code></pre></div></div>
<p>Estos parámetros reemplaza a <strong>todo</strong> lo que esté definido con CMD en el Dockerfile</p>

<p>Si hago esto dará error</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="nx">hola</span><span class="w">
</span></code></pre></div></div>

<p>porque sobre-escribe todo, y el comando que intenta ejecutar es “chau” que no es válido. Entonces <strong>CMD</strong> me permite sobre-escribir todo pero no me da la posibilidad de cambiar parte de lo que es el comando original.</p>

<h4 id="qué-pasa-si-quiere-pasar-parámetros-pero-que-se-agregar-a-mi-ejecutable-o-script">¿Qué pasa si quiere pasar parámetros pero que se agregar a mi ejecutable o script?</h4>

<p>Para esto existe Entrypoint, si pasamos parámetros en la línea de comandos se toman como argumentos del Entrypoint, es como si se agregasen, por ejemplo esto:</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["echo"]</span>
</code></pre></div></div>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="nx">hola</span><span class="w">
</span></code></pre></div></div>

<p>En el ejemplo anterior daría error porque no existe un comando “hola” pero este caso imprime “hola” porque se pasa como argumento a <em>echo</em></p>

<p>Genial, pero qué pasa si queremos tener un argumento por defecto que se pueda sobre-escribir, bien, combinamos ambos.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["echo"]</span>
<span class="k">CMD</span><span class="s"> ["hola"]</span>
</code></pre></div></div>

<p>Viéndolo así es simple, pero también podemos sobre-escribir el entrypoint en caso que querramos hacerlo</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nt">--entrypoint</span><span class="w"> </span><span class="nx">ls</span><span class="w"> </span><span class="nx">test</span><span class="w">
</span></code></pre></div></div>
<p>Y sobre-escribimos el entrypoint y el cmd, de este modo tenemos todas opciones para poder evitar que el comando por defecto del Dockerfile se ejecute para lograr tener un contenedor estable que nos permita trabajar.</p>

<h2 id="kubernetes">Kubernetes</h2>
<p>Y cómo se hace todo esto en Kubernetes, con dos opciones, pero lo confuso es el nombre</p>

<ul>
  <li>args: es equivalente a CMD en Docker</li>
  <li>COMMAND: es equivalente a ENTRYPOINT en Docker</li>
</ul>

<p>entonces, para el ejemplo anterior en un Pod quedaría así:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">creationTimestamp</span><span class="pi">:</span> <span class="no">null</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">run</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">image</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">echo"</span><span class="pi">]</span>
    <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">hola</span><span class="nv"> </span><span class="s">desde</span><span class="nv"> </span><span class="s">pod"</span><span class="pi">]</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">resources</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="na">dnsPolicy</span><span class="pi">:</span> <span class="s">ClusterFirst</span>
  <span class="na">restartPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">status</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>
<p>Y listo, un ejemplo de cómo tener un Pod que se quede esperando para poder ingresar en él sería:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">creationTimestamp</span><span class="pi">:</span> <span class="no">null</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">run</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">image</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">tail"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-f"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">/dev/null"</span><span class="pi">]</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">resources</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="na">dnsPolicy</span><span class="pi">:</span> <span class="s">ClusterFirst</span>
  <span class="na">restartPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">status</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>

<p>y ya podemos ingresar a su línea de comandos</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="n">kubectl</span><span class="w"> </span><span class="nx">exec</span><span class="w"> </span><span class="nt">-it</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="nx">sh</span><span class="w">
</span></code></pre></div></div>

<p>Nos leemos.</p>]]></content><author><name>Leonardo Micheloni</name></author><category term="k8s" /><category term="kubernetes" /><category term="docker" /><category term="entrypoint" /><category term="cmd" /><category term="args" /><category term="commads" /><category term="troubleshooting" /><summary type="html"><![CDATA[Si estamos desplegando en Kubernetes en probable que si algo sale mal y necesitamos hacer troubleshooting, entre las herramientas más útiles (quitando los logs) poder ejecutar un comando o “entrar” en un container puede ser muy útil. El problema es si nuestro ejecutable es el que hace que el Pod se reinicie y no podemos obtener acceso al bash del Pod, una forma de solucionar eso es sobre-escribir el comando por defecto, vamos a hablar de eso.]]></summary></entry><entry><title type="html">Acceder a un repository de imágenes con autenticación desde Kubernetes</title><link href="https://leomicheloni.com/k8s-acr-auth/" rel="alternate" type="text/html" title="Acceder a un repository de imágenes con autenticación desde Kubernetes" /><published>2022-03-01T00:00:00+00:00</published><updated>2022-03-01T00:00:00+00:00</updated><id>https://leomicheloni.com/k8s-acr-auth</id><content type="html" xml:base="https://leomicheloni.com/k8s-acr-auth/"><![CDATA[<p>Los Pods en <strong>Kubernetes</strong> utilizan imágenes, y lo más probable es que estas imágenes estén en un registro fuera del cluster, y lo segundo más probable es que necesite autenticación. Entonces nos encontramos con la pregunta <strong>“Cómo hago para que un Pod o Deployment en Kubernetes se autentique para descargar una imagen desde un repositorio que no es público?”</strong>. Bien, es post es acerca de cómo resolver este caso en Azure Container Registry, pero podría ser cualquier otro repository.</p>

<h1 id="secrets-al-rescate">Secrets al rescate</h1>
<p>La respuesta es bastante sencilla, necesitamos crear un secret, pero de un tipo especial en este caso <strong>kubernetes.io/dockerconfigjson</strong> sin embargo como casi siempre con los secrets lo más adecuado es crearlos en el momento de utilizarlos y no dejar el YAML en en repository de código porque pierden un poco el sentido, el comando para crearlo sería así:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kubectl</span><span class="w"> </span><span class="nx">create</span><span class="w"> </span><span class="nx">secret</span><span class="w"> </span><span class="nx">docker-registry</span><span class="w"> </span><span class="nx">mysecreto</span><span class="w"> </span><span class="nt">--docker-username</span><span class="o">=</span><span class="n">username</span><span class="w"> </span><span class="nt">--docker-password</span><span class="o">=</span><span class="n">password</span><span class="w"> </span><span class="nt">--docker-server</span><span class="o">=</span><span class="n">urldemiregistro</span><span class="w">
</span></code></pre></div></div>

<p>Y el resultado es el siguiente (notemos que ya el password está encriptado)</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">kubernetes.io/dockerconfigjson</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">.dockerconfigjson</span><span class="pi">:</span> <span class="s">eyJhdXRocyI6eyJ1cmxkZW1pcmVnaXN0cm8iOnsidXNlcm5hbWUiOiJ1c2VybmFtZSIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJhdXRoIjoiZFhObGNtNWhiV1U2Y0dGemMzZHZjbVE9In19fQ==</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Secret</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">creationTimestamp</span><span class="pi">:</span> <span class="no">null</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">mysecreto</span>
</code></pre></div></div>
<h1 id="cómo-usamos-el-secret-para-acceder-en-nuestro-pod--deployment">¿Cómo usamos el secret para acceder en nuestro pod / deployment?</h1>
<p>Bien, esto es relativamente sencillo, solo debemos agregar una opción a nivel de containers (que es imagepullsecrets, porque podrían ser varios)</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">run</span><span class="pi">:</span> <span class="s">busybox</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">busybox</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">imagePullSecrets</span><span class="pi">:</span> <span class="c1"># indicamos que si se necesita autenticación se utilice el user y password del secret</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">mysecreto</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">image</span><span class="pi">:</span> <span class="s">busybox</span>
    <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">tail"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-f"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">/dev/null"</span><span class="pi">]</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">busybox</span>
    <span class="na">resources</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="na">dnsPolicy</span><span class="pi">:</span> <span class="s">ClusterFirst</span>
  <span class="na">restartPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">status</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>

<p><a href="https://github.com/leomicheloni/leomicheloni.github.io/tree/master/postexamples/20220301">Dejo los ejemplos por acá</a></p>

<p>Y listo, nos leemos en la próxima.</p>]]></content><author><name>Leonardo Micheloni</name></author><category term="k8s" /><category term="kubernetes" /><category term="docker" /><category term="acr" /><category term="azure" /><category term="security" /><summary type="html"><![CDATA[Los Pods en Kubernetes utilizan imágenes, y lo más probable es que estas imágenes estén en un registro fuera del cluster, y lo segundo más probable es que necesite autenticación. Entonces nos encontramos con la pregunta “Cómo hago para que un Pod o Deployment en Kubernetes se autentique para descargar una imagen desde un repositorio que no es público?”. Bien, es post es acerca de cómo resolver este caso en Azure Container Registry, pero podría ser cualquier otro repository.]]></summary></entry><entry><title type="html">oAuth2 paso a paso “¿Qué es oAuth?”</title><link href="https://leomicheloni.com/oauth-1/" rel="alternate" type="text/html" title="oAuth2 paso a paso “¿Qué es oAuth?”" /><published>2021-12-01T00:00:00+00:00</published><updated>2021-12-01T00:00:00+00:00</updated><id>https://leomicheloni.com/oauth-1</id><content type="html" xml:base="https://leomicheloni.com/oauth-1/"><![CDATA[<p>oAuth es prácticamente la opción más utilizada para securizar aplicaciones modernas por diferentes motivos, éste es el primer de una serie de post sobre oAuth2, intentando aclarar cada concepto paso a paso. Si bien es un tema bastante complejo pero también muy importante y en lo personal creo que todo desarrollador debería comprenderlo bien.</p>

<p>También veremos <strong>OpenID Connect</strong> como parte de esta serie ya que están muy relacionados.</p>

<p>A fines prácticos vamos a hablar siempre de <strong>oAuth2</strong>.</p>

<p><img src="../images/oauth-logo.webp" alt="" /></p>

<h1 id="qué-es-oauth2">¿Qué es oAuth2?</h1>

<p>“<a href="https://oauth.net/2/">oAuth es un protocolo standard</a>, de <strong>autorización</strong>, que provee flujos específicos para aplicación web, desktop, mobile y disponistivos de la vida diaría”
Algo así dice su definición, así que vamos a aclarar de a poco cada concepto.</p>

<h2 id="autorización-vs-autenticación">Autorización vs autenticación</h2>

<p>Lo primero es diferenciar autorización de autenticación:</p>

<blockquote>
  <p>Autorizado es que tiene permisos para hacer algo.</p>
</blockquote>

<blockquote>
  <p>Autenticado es que se puede verificar quién es.</p>
</blockquote>

<h3 id="estar-autorizado">Estar autorizado</h3>

<p>El ejemplo más claro para mí es un ticket de cine, el mismo no tiene nombre, ni dice nada sobre la persona que lo compró, pero <strong>autoriza</strong> a quien lo tenga a hacer algo, en este caso a entrar a ver una película, y solo lo autoriza a hacer eso.
Es importante tener en cuenta que no importa si yo compré el ticket, quien lo tenga podrá entrar al cine aunque lo haya encontrado en el suelo.</p>

<p>El simple hecho de poseer el ticket (vamos a llamarlo <strong>Token</strong>) nos habilita a hacer algo independientemente de nuestra identidad, y de hecho, quien nos autorice no necesita saber nada sobre nosotros, solo que tenemos ese Token (en este caso en forma de un ticket de cine)</p>

<h3 id="estar-autenticado">Estar autenticado</h3>

<p>En este caso la <strong>autenticación</strong> verifica que somos quienes decimos ser, pero no implica que estemos autorizados.
Un ejemplo puede ser un documento, permite verificar datos propios pero no implica que tenga permisos de hacer algo, por ejemplo, pasar una frontera, o volviendo al ejemplo anterior ingresar al cine, si no tengo un ticket no puedo entrar al cine por más que tenga un DNI o pasaporte.
<strong>oAuth no provee mecanismos de autenticación</strong></p>

<h3 id="algunas-aclaraciones-sobre-openid-connect-oidc">Algunas aclaraciones sobre OpenID Connect (OIDC)</h3>

<p>Por simplificar, a veces se llama OIDC cuando tenemos ambas cosas, oAuth y OIDC porque este último <strong>es una extensión del primero</strong>, pero siendo rigurosos OIDC provee una capa de autenticación a oAuth que es un protocolo de autenticación.</p>

<h2 id="qué-problema-intenta-solucionar-oauth">¿Qué problema intenta solucionar oAuth?</h2>

<p>Sin entrar en historias largas, el problema que intentams solucionar es:</p>

<blockquote>
  <p>Autorizar a un cliente, sin exponer credenciales ni identidad, dando un acceso solo un conjunto de recursos por un tiempo limitado.</p>
</blockquote>

<h2 id="algo-de-vocabulario">Algo de vocabulario</h2>

<p>Hay mucho vocabolario de oAuth y es bueno comenzar a familiarizarnos con él, comencemos con lo más básico.</p>

<h3 id="quién-es-un-cliente">¿Quién es un cliente?</h3>
<p>Un cliente será cualquier aplicación que quiera tener acceso a un recuso. Nunca será una persona física.
Algunos ejemplos:</p>
<ul>
  <li>Un sitio web</li>
  <li>Un frontend que quiere acceder a una API</li>
  <li>Un backend que accede a otro backend</li>
</ul>

<h3 id="qué-son-recursos">¿Qué son recursos?</h3>
<p>Llamamos recursos a todo aquello a lo que podemos dar un nivel de acceso:</p>
<ul>
  <li>Una aplicación</li>
  <li>Una parte de la aplicación</li>
  <li>Una API</li>
  <li>Un sistema</li>
  <li>Un conjunto de información</li>
  <li>Una funcionalidad del protocolo</li>
  <li>Etc.</li>
</ul>

<h3 id="y-quién-se-encarga-de-dar-acceso-a-los-clientes-sobre-esos-recursos">¿Y quién se encarga de dar acceso a los clientes sobre esos recursos?</h3>
<p>Esta entidad se llama <strong>Identity provider o IDP</strong> (por ejemplo Facebook), es quien conoce a los usuarios y otorga permisos a los clientes (el sitio de Adobe) a acceder a mi información (el recurso).</p>

<h2 id="algunos-ejemplos-de-uso-de-oauth">Algunos ejemplos de uso de oAuth</h2>
<p>En Instagam podemos tener la opción de que al crear una publicación se publique también en Twitter, esto es un buen ejemplo de oAuth.</p>

<p>Instagram quiere publicar en Twitter en nuestro nombre, bien, entonces:</p>
<ul>
  <li>Instagram es el cliente</li>
  <li>La API de Twitter es el recurso</li>
  <li>Twitter es también el IDP (porque somos usuarios de Twitter)</li>
</ul>

<p>Entonces, Twitter nos conoce como usuarios y también a Instagram como cliente, y simplemente le da acceso a la API de publicación en nuestro nombre por un tiempo limitado a través de un Token. De este modo solucionamos muchos problemas.</p>

<p>Lo mismo pasa cuando queremos importar contactos de una red social a otra, al final, <strong>concentimos</strong> acceso a un recurso que es nuestro (nuestros contactos, nuestro timeline, es así que nuestro rol es de <strong>Resource owner</strong>) a un cliente (Instagram o quién sea) y ese acceso lo otorga el IDP proveyendo un Token al cliente.</p>

<h3 id="identity-provider">Identity provider</h3>
<p>El IDP es el responsable de otorgar acceso, es quien conoce a los clientes y los recursos, cuando un cliete solicita acceso a un recurso el IDP le provee un Token, el <strong>Resource Provider</strong> (quien tiene el recurso, por ejemplo Twitter) verificar que ese Token es válido.</p>

<h4 id="quien-gestiona-los-recursos-no-siempre-es-también-el-idp">Quien gestiona los recursos no siempre es también el IDP</h4>

<p>El IDP puede no ser quien gestione los recusos como hemos dicho, solo quien gestiona los accesos, por ejemplo en Google tenemos un único login (IDP) con el cual accedemos a muchos servicios (Recursos) que no están en el mismo dominio (y de hecho, no son la misma aplicación ni nada).
Entonces el login central de Google es el IDP y la API de GMaps será el <strong>Resource Provider</strong>, y la misma estará separada en recursos, por ejemplo, la capacidad de buscar una dirección será un Recurso particular sobre el que podemos perdir acceso, pero tal vez no podamos hacerlo para crear puntos de interés en el mapa.</p>

<blockquote>
  <p>Nota: es por eso que en 2021 hubo un fallo global en los servicios de Google porque falló el servicio de autenticación (IDP) que todos los otros servicios usan.</p>
</blockquote>

<h2 id="repasando-el-vocabulario">Repasando el vocabulario</h2>
<ul>
  <li>Recurso: Un elemento al que se puede otorgar acceso, como una API, un dato, una aplicación, etc.</li>
  <li>Token: Un elemento que sirve para otorgar el acceso, en forma de un conjunto de caracteres (un GUID, un número, o algo más complejo como un JSON Web Token), suelen tener un acceso limitado a ciertos recursos por un tiempo limitado.</li>
  <li>IDP: Es quien otorga Tokens, quien conoce a los usuarios y a los clientes.</li>
  <li>Cliente: Una aplicación que quiere acceder a un recurso.</li>
  <li>Resource Provider: Quien gestiona los recursos, verificar que las solicitudes de acceso de los clientes son válidas para el IDP configurado</li>
  <li>Resource Owner: El propietario de los recursos, no siempre son personas, en el caso de que el recurso sea una API el RO será la aplicación, pero en caso de nuestro muro de Facebook seremos nosotros.</li>
</ul>

<p>Más o menos los actores se relacionan así (de modo simplificado)</p>

<p><img src="../images/oauthactors.png" alt="" /></p>

<p>Bien, en la próxima continuamos hablando sobre Tokens. Nos leemos.</p>]]></content><author><name>Leonardo Micheloni</name></author><category term="oauth" /><category term="oidc" /><category term="authorization" /><category term="web" /><category term="mobile" /><category term="api" /><summary type="html"><![CDATA[oAuth es prácticamente la opción más utilizada para securizar aplicaciones modernas por diferentes motivos, éste es el primer de una serie de post sobre oAuth2, intentando aclarar cada concepto paso a paso. Si bien es un tema bastante complejo pero también muy importante y en lo personal creo que todo desarrollador debería comprenderlo bien.]]></summary></entry></feed>