<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jhorman Villanueva</title>
    <description>The latest articles on DEV Community by Jhorman Villanueva (@jhorvi24).</description>
    <link>https://dev.to/jhorvi24</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3378338%2F908d5417-bc8d-4cb0-8671-1397d9eee413.jpg</url>
      <title>DEV Community: Jhorman Villanueva</title>
      <link>https://dev.to/jhorvi24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jhorvi24"/>
    <language>en</language>
    <item>
      <title>Orquestando contenedores serverless para IoT con AWS ECS Fargate</title>
      <dc:creator>Jhorman Villanueva</dc:creator>
      <pubDate>Wed, 08 Jul 2026 21:09:09 +0000</pubDate>
      <link>https://dev.to/jhorvi24/orquestando-contenedores-serverless-para-iot-con-aws-ecs-fargate-340g</link>
      <guid>https://dev.to/jhorvi24/orquestando-contenedores-serverless-para-iot-con-aws-ecs-fargate-340g</guid>
      <description>&lt;p&gt;En este blog voy a construir una plataforma IoT sobre AWS usando el servicio Amazon ECS como orquestador de contenedores integrando dos tecnologías que me apasionan: computación en la nube e IoT. Un orquestador de contenedores permite gestionar y ejecutar múltiples contenedores donde cada uno cumple una función específica, siguiendo una arquitectura de microservicios. Toda la infraestructura será aprovisionada con Terraform, aplicando las buenas prácticas de Infraestructura como código (IaC). Esta infraestructura soportará el flujo completo de datos: desde un dispositivo IoT que envía lecturas de sus sensores, hasta su procesamiento, almacenamiento y visualización en la nube.  &lt;/p&gt;

&lt;p&gt;Empecemos hablando entonces un poco de lo que es IoT. &lt;/p&gt;

&lt;p&gt;La mayoría de los proyectos en IoT (Internet de Things) se basan en la siguiente arquitectura de referencia de 3 capas propuesta por Miao Wu e investigadores de la universidad de Beijing en el año 2010. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjsdijwaqu1qchn4yqzs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjsdijwaqu1qchn4yqzs.png" alt="iot-reference-architecture" width="531" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la capa de percepción se tiene los dispositivos IoT con la capacidad de medir diferentes variables de un entorno físico como temperatura, humedad, luminosidad, movimiento, pH, entre otras, o actuar en el entorno como encender o apagar un bombillo, activar una alarma o abrir una puerta, etc. &lt;/p&gt;

&lt;p&gt;En la capa de red, estos dispositivos IoT integran protocolos y tecnologías de comunicación ya sea para interactuar entre sí (M2M) o para enviar y recibir datos hacia y desde la nube a través de internet. &lt;/p&gt;

&lt;p&gt;Y en la capa de aplicación es donde se almacenan, procesan, se generan alertas, se visualizan los datos, etc, es decir que es la capa de donde se le da valor a los datos medidos u obtenidos a través de la capa de percepción. &lt;em&gt;Si quieres obtener mas información sobre Internet de las Cosas te invito a leer el siguiente blog donde profundizo sobre IoT y muestro cómo implementar una arquitectura serverless para IoT&lt;/em&gt;: [&lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66"&gt;https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Tomando como base la anterior arquitectura de 3 capas, en el presente blog vamos a implementar cada una de esas capas de la siguiente forma:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capa de percepción&lt;/strong&gt;: usaremos un ESP32 con un sensor DHT11. El ESP 32 se caracteriza por su bajo costo y por tener integrado interfaces de comunicación inalámbricas como Bluetooth y WI-Fi. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz069kk4wbctnkrck9q0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz069kk4wbctnkrck9q0i.png" alt="esp32 with dht sensor" width="672" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capa de red&lt;/strong&gt;: gracias al WI-Fi integrado del ESP32, podemos conectarnos a una red para enviar datos a un servidor local o directamente a un servidor en la nube a través de internet. WI-Fi es una tecnología de acceso a la red que transporta tráfico IP, operando en las capas 1 y 2 del modelo OSI. Para poder enviar los datos al servidor, ya sea local o en la nube, se utilizan protocolos de capa 7 como HTTP o MQTT.  Aunque MQTT es más ligero y eficiente que HTTP, en la presente implementación trabajaremos con HTTP. A continuación, muestro parte del código usado en el ESP32:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;


  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WiFi connection lost. Reconnecting..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WiFi connected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to IP address: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localIP&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; 

  &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readHumidity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readTemperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Humidity: "&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Temperature: "&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"°C "&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httpPort&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to server"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;//Construir JSON&lt;/span&gt;
  &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;postData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;humidity&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="c1"&gt;//String postData = "humidity=" + String(h) + "&amp;amp;temperature=" + String(t);  &lt;/span&gt;


  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Host: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection: close"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Length: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  


  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readStringUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Headers received, response:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data sent and connection closed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection failed to server"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Capa de aplicación&lt;/strong&gt;: aquí es donde implementaremos el orquestador de contenedores en AWS. Antes de ir con la implementación, vale la pena destacar que AWS ofrece dos servicios de orquestación de contenedores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Elastic Container service (Amazon ECS)&lt;/strong&gt;: orquestador nativo de AWS que se caracteriza por su simplicidad y fácil integración con otros servicios de AWS. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Elastic Kubernetes Service (Amazon EKS)&lt;/strong&gt;: basado en el proyecto de código abierto Kubernetes donde AWS administra parte de su arquitectura. Es la opción ideal cuando ya tienes un proyecto de kubernetes On-Premises y quieres migrar a la nube de AWS. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para la implementación de la presente plataforma IoT vamos a usar el servicio ECS pero primero entendamos su arquitectura. En Amazon ECS se identifican los siguientes componentes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Task Definition (Definición de tarea): permite configurar el contenedor que va a ejecutar un servicio específico. En ella se especifica:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imagen del contenedor &lt;/li&gt;
&lt;li&gt;El puerto&lt;/li&gt;
&lt;li&gt;El sistema operativo &lt;/li&gt;
&lt;li&gt;Los requisitos de CPU y memoria&lt;/li&gt;
&lt;li&gt;Las variables de entorno&lt;/li&gt;
&lt;li&gt;Dos roles de IAM importantes: el &lt;em&gt;Task Role&lt;/em&gt;, que permite que la aplicación dentro del contenedor interactúe con otros servicios de AWS, y el T_ask Execution Role_, que opera a nivel de infraestructura y otorga permisos al agente de contenedores de Amazon ECS para ejecutar tareas como descargar imágenes o escribir logs. 
Todo esto se puede configurar desde la consola de AWS o mediante una plantilla en formato JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task (Tarea)&lt;/strong&gt;: es la unidad básica de ejecución en Amazon ECS. Se puede interpretar como una instancia de una Task Definition. Representa la ejecución real de la aplicación. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Services (servicios)&lt;/strong&gt;: es el componente que permite ejecutar y mantener un número definido de task (tareas).  Es importante para lograr:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alta Disponibilidad&lt;/strong&gt;: si una task muere o no responde, el service lanza otra automáticamente. Se puede integrar con un balanceador de carga (Application Load Balancer/Network Load Balancer) para distribuir tráfico. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alta Escalabilidad&lt;/strong&gt;: se define cuántas replicas se quiere lanzar. Se puede integrar con un Auto Scaling Group. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clúster&lt;/strong&gt;: es un grupo lógico de recursos donde se ejecutan las tareas y servicios en la infraestructura de capacidad registrada en Amazon ECS. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El despliegue o lanzamiento de los contenedores se puede realizar en:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Fargate&lt;/strong&gt;: solución serverless donde no hay necesidad de administrar servidores, solo se define los recursos a nivel de las tasks y se paga por lo que se consume. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon EC2&lt;/strong&gt;: permite control sobre el tipo y cantidad de instancias (máquinas virtuales) que conforman el clúster.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para entender mejor el funcionamiento de Amazon ECS me gusta hacer la analogía con el siguiente caso:&lt;/p&gt;

&lt;p&gt;Pensemos en el servicio que ofrece un hotel cinco estrellas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwxg949w832wxw4tueebg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwxg949w832wxw4tueebg.png" alt="hotel example" width="563" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;El hotel tiene un &lt;em&gt;director&lt;/em&gt; o &lt;em&gt;administrador&lt;/em&gt; que sería &lt;em&gt;Amazon ECS&lt;/em&gt; administrado por AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cada habitación tiene un &lt;em&gt;manual de procedimiento&lt;/em&gt; que indica cómo debe estar organizada la habitación. Esto corresponde a una &lt;em&gt;Task Definition (Definición de Tarea)&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Una habitación ocupada sigue el &lt;em&gt;manual de procedimiento (task definition)&lt;/em&gt;. Para este caso la habitación ocupada corresponde a una &lt;em&gt;task&lt;/em&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;En la recepción debe haber alguien que se encargue que siempre haya habitaciones listas, por ejemplo debe asegurar que siempre haya 5 habitaciones listas, si una se desocupa o tiene un problema, prepara otra inmediatamente. Ese alguien de la recepción sería el &lt;em&gt;Service (servicio).&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El edificio físico del hotel es el &lt;em&gt;Clúster _y su _capacidad&lt;/em&gt;, que corresponde a EC2 o Fargate, es el terreno o los cimientos sobre los que se construye. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ya teniendo claro en qué consiste Amazon ECS, veamos los contenedores que vamos a implementar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;webserver-iot&lt;/strong&gt;: servidor web desarrollado en Python con Flask, encargado de recibir los datos enviados por el ESP32 a través de HTTP, procesarlos y almacenarlos en la base de datos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;InfluxDB&lt;/strong&gt;: base de datos de series de tiempo donde se almacenan los datos medidos por los sensores.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grafana&lt;/strong&gt;: herramienta de visualización donde configuraremos un dashboard para monitorear los datos de los sensores en tiempo real. Además, permite configurar alertas y notificaciones para actuar de forma proactiva ante cualquier anomalía detectada.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Con base en estos contenedores y en los requerimientos de una plataforma IoT, la arquitectura a implementar es la siguiente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdgg198oxcpeeuha5dm1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdgg198oxcpeeuha5dm1l.png" alt="architecture design" width="800" height="758"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Antes de ir con la implementación del orquestador de contenedores, analicemos cómo implementar el servidor web para IoT que después vamos a contenerizar. &lt;/p&gt;

&lt;h4&gt;
  
  
  webserver-iot
&lt;/h4&gt;

&lt;p&gt;Para el servidor web IoT primero vamos a desarrollar la aplicación usando Python y el framework de desarrollo de aplicaciones web Flask. A continuación, comparto parte del código.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data_received&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request arrive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;humidity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;humidity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Humidity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;humidity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%, temperature: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;°C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;save_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;humidity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;save_to_influxdb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;humidity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Data received&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.14-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /webserver-IoT&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt /webserver-IoT/requirements.txt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /webserver-IoT&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "./server-iot.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos la imagen Docker&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build -t web-server-iot .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Como la idea es desplegar el contenedor a través del servicio Amazon ECS, subimos esta imagen desde mi repositorio local al servicio de Amazon ECR. Primero creamos el repositorio. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws ecr create-repository --repository-name web-server-iot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ya teniendo el repositorio creado realizamos un push de la imagen docker que tenemos local al repositorio Amazon ECR. El mismo servicio nos explica el paso a paso para realizar el push.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxgyr9snasichut7ro9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxgyr9snasichut7ro9y.png" alt="description to push the image to ecr" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementación del servicio Amazon ECS
&lt;/h2&gt;

&lt;p&gt;Usaremos Terraform para aprovisionar toda la infraestructura, que incluye la red, el clúster de Amazon ECS y la configuración de los services y task definitions. Siguiendo las buenas prácticas de IaC, configuraremos un backend remoto en Amazon S3 para almacenar el estado de Terraform y gestionar el lock de estado, evitando modificaciones concurrentes. El código está organizado por módulos, puedes encontrar el código completo en Github: &lt;a href="https://github.com/jhorvi24/aws-ecs-serverless-iot.git" rel="noopener noreferrer"&gt;github-aws-ecs-serverless&lt;/a&gt; , donde se configura:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;El cluster de amazon ECS&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En este módulo se define el nombre del cluster, las tasks definitions y los Services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"IoT-cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IoT-cluster"&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para el &lt;em&gt;IoT-Cluster&lt;/em&gt; se configuran 3 &lt;em&gt;Task Definitions&lt;/em&gt; y los respectivos &lt;em&gt;Services&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task definitions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En la task definitions definimos la arquitectura de CPU, el sistema operativo sobre el cual van a correr las Tasks (tareas), la cantidad de CPU y memoria RAM que se reserva para que las diferentes Tasks se ejecuten, estos valores dependen de cuánto consume la aplicación o aplicaciones. &lt;/p&gt;

&lt;p&gt;Y configuramos también las características del contenedor: nombre, URI de la imagen, puerto, variables de entorno (environment variables), entre otros.  Dentro de una task definitions podemos configurar un único contenedor o varios, y esta decisión tiene implicaciones importantes. &lt;/p&gt;

&lt;p&gt;Si en una misma task definitions lanzamos los 3 contenedores -web server IoT, InfluxDB y Grafana-, todos correrán sobre el mismo host, lo que significa que la comunicación se hace mediante localhost y el puerto correspondiente. El problema de este enfoque es que los contenedores quedan estrechamente acoplados: no pueden escalar de forma independiente, comparten recursos entre sí y pierden disponibilidad ya que, si un contenedor falla por alguna razón, lo más probable es que las tasks se caigan y por ende todos los contenedores.&lt;/p&gt;

&lt;p&gt;Existe un caso específico donde si tiene sentido configurar mas de un contenedor por task definition: el Sidecar Pattern. En este patrón se tiene un contenedor principal y un contenedor auxiliar que le proporciona capacidades adicionales, como logging, monitoreo o seguridad, sin mezclar responsabilidades en el contenedor principal.&lt;br&gt;
Para la presente implementación se configuraron 3 task definitions independientes lo que nos permite escalar, desplegar y monitorear cada servicio de forma autónoma, favoreciendo un entorno altamente disponible. &lt;/p&gt;

&lt;p&gt;Las 3 task definitions son: &lt;/p&gt;
&lt;h4&gt;
  
  
  webserver-iot-task-definition
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-task-definition"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-taskdefinition-tf"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3 GB"&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1 vCPU"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot"&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"001239102331.dkr.ecr.us-east-1.amazonaws.com/web-server-iot:latest"&lt;/span&gt;
      &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INFLUX_DB"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sensor-db"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INFLUX_HOST"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://influxdb-core.db.local:8181"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INFLUX_TOKEN"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
          &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
        &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/webserver-iot"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-create-group"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;



  &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;La imagen del contenedor se encuentra en le repositorio de ECR. &lt;/p&gt;

&lt;p&gt;En esta task definition configuramos las siguientes variables de entorno: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;INFLUX_HOST&lt;/strong&gt;: corresponde al endpoint de InfluxDB. Como cada contenedor corre en su propia task deninitions, necesitamos un Service Discovery para que los servicios se puedan comunicar entre sí (mas adelante explicamos este concepto y cómo se configura). El valor de esta variable es la URL asignada por Service Discovery. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;INFLUX_DB&lt;/strong&gt;: nombre de la base de datos en InfluxDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;INFLUX_TOKEN&lt;/strong&gt;: como InfluxDB está configurado en modo sin autenticación, esta variable se deja vacía. En un entorno productivo se recomienda configurar un token y asignarlo aquí para mejorar la seguridad.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;influxdb-task-definition&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-task-definition"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-taskdefinition-tf"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3 GB"&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1 vCPU"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-core"&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"influxdb:3-core"&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"influxdb3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--node-id=my-node-0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--object-store=file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--data-dir=/var/lib/influxdb3/data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--plugin-dir=/var/lib/influxdb3/plugins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--without-auth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8181&lt;/span&gt;
          &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8181&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
        &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/influxdb-core"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-create-group"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;



  &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Para esta tasks definitions usamos InfluxDB 3 cuya imagen del contenedor se descarga automáticamente desde Docker Hub. Como mencionamos anteriormente, InfluxDB permite configurar un token por seguridad y autenticación, pero para temas prácticos se va a dejar sin token. En la sección &lt;strong&gt;&lt;em&gt;container_definitions&lt;/em&gt;&lt;/strong&gt;, el parámetro &lt;strong&gt;_command _&lt;/strong&gt;se configura de la siguiente forma:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;command = ["influxdb3", "serve", "--node-id=my-node-0", "--object-store=file", "--data-dir=/var/lib/influxdb3/data", "--plugin-dir=/var/lib/influxdb3/plugins", "--without-auth"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Destacamos el flag --without-auth, que es el que deshabilita la autenticación, y --data-dir, que define la ruta donde InfluxDB almacenará los datos dentro del contenedor.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;grafana-task-definition&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"grafana-task-definition"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-taskdefinition-tf"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3 GB"&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1 vCPU"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-iot"&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana/grafana-enterprise"&lt;/span&gt;
      &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
          &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
        &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/grafana-iot"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-create-group"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



  &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;La imagen del contenedor se descarga del DockerHub. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se configuran 3 &lt;em&gt;Services&lt;/em&gt; para cada &lt;em&gt;Task Definitions&lt;/em&gt; los cuáles son:&lt;/p&gt;
&lt;h4&gt;
  
  
  web-server-iot-services
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-service"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IoT-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webserver-iot-task-definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;desired_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;
  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-privada-A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-privada-B&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webserver-iot-sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;assign_public_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webserver-iot-tg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;container_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot"&lt;/span&gt;
    &lt;span class="nx"&gt;container_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Se configuran dos réplicas, una por cada zona de disponibilidad, distribuidas en subredes privadas para mayor seguridad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;En network_configuration se asocian las dos subredes privadas y el grupo de seguridad correspondiente. Al estar en subredes privadas, las tareas no necesitan IP pública — el tráfico llega a través del ALB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Se asocia un Application Load Balancer (ALB) para distribuir el tráfico entre las réplicas y garantizar alta disponibilidad, junto con su respectivo Target Group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El launch_type se configura como Fargate (Serverless), lo que nos permite ejecutar contenedores sin administrar servidores ni planificar capacidad de infraestructura.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  influxdb-service
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-service"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IoT-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;influxdb-task-definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;desired_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;
  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-privada-AA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db-sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;service_registries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;registry_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_service_discovery_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;influxdb-service-discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Se configura una réplica en una subred privada, ya que la base de datos puede almacenar datos sensibles y no debe estar expuesta públicamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El tipo de lanzamiento se configura como Fargate, manteniendo el enfoque serverless de toda la plataforma.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Como cada contenedor tiene su propia task definition, necesitamos un mecanismo de red que permita la comunicación entre los diferentes servicios. Amazon ECS ofrece dos opciones para esto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service Connect: permite que las aplicaciones se comuniquen entre sí usando nombres abreviados y puertos estándar.&lt;/li&gt;
&lt;li&gt;Service Discovery: se integra con AWS Cloud Map para mantener un nombre de host DNS que se resuelve en la dirección IP interna de cada tarea.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para esta implementación optamos por Service Discovery, ya que nos proporciona un nombre DNS estable que el webserver-iot utiliza para conectarse a InfluxDB a través de la variable de entorno INFLUX_HOST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_service_discovery_private_dns_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"db_local"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.local"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Namespace for database services"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot-ecs-red&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_service_discovery_service"&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-service-discovery"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"influxdb-core"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_service_discovery_private_dns_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;dns_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;namespace_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_service_discovery_private_dns_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;routing_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MULTIVALUE"&lt;/span&gt;
    &lt;span class="nx"&gt;dns_records&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ttl&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  grafana-service
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"grafana-service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-service"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IoT-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grafana-task-definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;desired_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;
  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-privada-AA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grafana-sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grafana-tg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;container_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-iot"&lt;/span&gt;
    &lt;span class="nx"&gt;container_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roles y políticas IAM&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En este módulo se configura los roles de IAM necesarios para el funcionamiento de la plataforma:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task role&lt;/strong&gt;: permite que los contenedores que corren dentro de las tasks interactúen con otros servicios de AWS — por ejemplo, almacenar objetos en Amazon S3 o consultar credenciales en AWS Systems Manager Parameter Store. Para esta implementación lo dejamos sin configurar, ya que nuestros contenedores no necesitan interactuar con otros servicios de AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task execution role&lt;/strong&gt;: permite al agente de ECS realizar operaciones de infraestructura como descargar imágenes desde Amazon ECR y escribir logs en CloudWatch. En Terraform lo definimos explícitamente con tres recursos: el rol con su política de confianza hacia &lt;code&gt;ecs-tasks.amazonaws.com&lt;/code&gt;, la política administrada &lt;code&gt;AmazonECSTaskExecutionRolePolicy&lt;/code&gt; y una política personalizada con permisos para crear y escribir logs en CloudWatch (&lt;code&gt;logs:CreateLogGroup&lt;/code&gt;, &lt;code&gt;logs:CreateLogStream&lt;/code&gt;, &lt;code&gt;logs:PutLogEvents&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Infraestructura de red&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En este módulo se configura la infraestructura de red que se basa en:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VPC&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2 subredes públicas&lt;/em&gt;: una por zona de disponibilidad, donde se ubican los NAT Gateways y el Application Load Balancer que recibe el tráfico del ESP32.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;3 subredes privadas&lt;/em&gt;: dos para las réplicas del webserver-iot, una por zona de disponibilidad, y una tercera compartida por InfluxDB y Grafana.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Internet Gateway&lt;/em&gt;: permite la comunicación entre la VPC e internet.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Route Tables&lt;/em&gt;: tablas de enrutamiento que dirigen el tráfico correctamente entre las subredes públicas y privadas.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2 NAT Gateways&lt;/em&gt;: uno por zona de disponibilidad, ubicados en las subredes públicas, permiten que los contenedores en subredes privadas salgan a internet sin estar expuestos públicamente — por ejemplo, para descargar imágenes desde Docker Hub o Amazon ECR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Balanceador de carga&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En este módulo se configura el Application Load Balancer (ALB) que recibe el tráfico HTTP del ESP32 y lo distribuye entre las réplicas del webserver-iot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"alb-iot"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alb-iot"&lt;/span&gt;
  &lt;span class="nx"&gt;internal&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb-iot-sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-publica-A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subred-publica-B&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alb-iot"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para el Application Load Balancer se configuran dos target_group:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Uno para la comunicación con las réplicas del webserver-iot&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-tg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"webserver-iot-tg"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot-ecs-red&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ip"&lt;/span&gt; &lt;span class="c1"&gt;#Obligatorio para fargate&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/health"&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"target-group-iot"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Y otro para la comunicación con grafana&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"grafana-tg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-tg"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iot-ecs-red&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ip"&lt;/span&gt; &lt;span class="c1"&gt;#Obligatorio para fargate&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/api/health"&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana-tg"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con la arquitectura clara, procedemos a desplegar la infraestructura con Terraform. Solo necesitamos dos comandos:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inicializa el proyecto, descarga los providers necesarios y configura el backend remoto en S3. (el bucket s3 debe haberse creado previamente)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Crea todos los recursos definidos en los módulos. Terraform mostrará un resumen de los cambios antes de aplicarlos y pedirá confirmación.&lt;/p&gt;

&lt;p&gt;Después que el aprovisionamiento de la infraestructura haya finalizado lo primero es verificar que el Service lance los tasks sin problema para cada task definitions y se encuentren en estado Runnig.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo7l12gwr324zojh7ghbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo7l12gwr324zojh7ghbn.png" alt="task running" width="798" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si las tasks se encuentran en el estado running se comprueba el acceso a Grafana a través del DNS del balanceador de carga&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Por defecto el user y password son: &lt;em&gt;admin&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1f59sxfalo580i5pnaw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1f59sxfalo580i5pnaw2.png" alt="grafana dashboard" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se configura a InfluxDB como fuente de datos&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31iimrcyxuun0fxo1ub7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31iimrcyxuun0fxo1ub7.png" alt="influxdb as connection" width="799" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se configuran los respectivos parámetros:&lt;/p&gt;

&lt;p&gt;URL: &lt;code&gt;http://influxdb-core.db.local:8181&lt;/code&gt;&lt;br&gt;
Database: &lt;code&gt;sensor-db&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6466n1sy3g3lcxcd6zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6466n1sy3g3lcxcd6zs.png" alt="parameters configurations" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para completar la configuración de InfluxDB como fuente de datos en Grafana, primero es necesario haber enviado al menos un dato desde el dispositivo IoT. Para ello, debemos actualizar el código del ESP32 reemplazando la dirección del servidor con el DNS del Application Load Balancer, el cual podemos obtener desde el output de Terraform o directamente desde la consola de AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alb-iot-1024585334.us-east-1.elb.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;httpPort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/data"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como el ESP32 envía datos de forma continua, podemos confirmar que el envío es exitoso monitoreando la salida desde el monitor serial del IDE de Arduino.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuqoo8q27zvjoyl5h47q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuqoo8q27zvjoyl5h47q.png" alt="confirmatoin data sent" width="625" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora procedemos con la configuración del dashboard en grafana.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobm78k6txwkzmn5yjv7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobm78k6txwkzmn5yjv7x.png" alt="dashboard configuration" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para consultar los datos almacenados en InfluxDB desde Grafana utilizamos InfluxQL, el lenguaje de consulta propio de InfluxDB, cuya sintaxis es similar a SQL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT temperature, humidity FROM "values_sensor" WHERE "device" = 'esp32-home' AND time &amp;gt;= now() - 6h&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqqh50t4zzi4pynstwcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqqh50t4zzi4pynstwcs.png" alt="grafana dashboard" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podemos cambiar la visualización de los datos&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvd8vs4udtbfywonqsx3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvd8vs4udtbfywonqsx3p.png" alt="grafana dashboard" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¡Y listo! Construimos una plataforma IoT production-ready sobre AWS: un ESP32 capturando datos de sensores, enviándolos a través de HTTP a un webserver-iot corriendo en Amazon ECS Fargate, almacenándolos en InfluxDB 3 y visualizándolos en tiempo real desde un dashboard de Grafana — todo aprovisionado con Terraform siguiendo buenas prácticas de Infraestructura como Código.&lt;br&gt;
Este proyecto demuestra que es posible construir una arquitectura IoT escalable, disponible y serverless en AWS sin administrar un solo servidor. El código completo está disponible en GitHub.&lt;br&gt;
En un próximo blog llevaremos esta arquitectura un paso más allá, reemplazando HTTP por MQTT para optimizar el consumo energético del dispositivo IoT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retos y mejoras
&lt;/h2&gt;

&lt;p&gt;Lo interesante de construir proyectos propios es que te enfrentas a desafíos reales que se convierten en aprendizaje y experiencia. Estos son los principales retos encontrados y las mejoras identificadas para futuras iteraciones:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alta disponibilidad&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Si se despliegan múltiples tasks de Grafana en diferentes zonas de disponibilidad, es necesario configurar un mecanismo de persistencia compartida para que los datos de sesión no se pierdan entre réplicas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si se despliegan múltiples tasks de InfluxDB en diferentes zonas de disponibilidad, se debe configurar la replicación de datos y el failover automático para garantizar consistencia y disponibilidad.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Migración de HTTP a MQTT&lt;/strong&gt;&lt;br&gt;
Actualmente el ESP32 envía datos periódicamente al ALB a través de HTTP. Sin embargo, si el dispositivo opera con batería y se requiere optimizar el consumo energético, MQTT es una mejor alternativa: es un protocolo ligero diseñado precisamente para entornos con recursos limitados. El problema es que un ALB no soporta MQTT, ya que opera en capa 7 sobre HTTP/HTTPS. La solución es reemplazarlo por un NLB, que trabaja en capa 4 y permite tráfico MQTT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service Mesh&lt;/strong&gt;&lt;br&gt;
Actualmente la comunicación entre servicios se gestiona mediante Service Discovery con AWS Cloud Map. Sin embargo, a medida que el proyecto crezca y se añadan más servicios, será importante migrar hacia un Service Mesh, que ofrece capacidades avanzadas como observabilidad del tráfico, balanceo de carga entre servicios y políticas de seguridad a nivel de red.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>containers</category>
      <category>aws</category>
      <category>fargate</category>
    </item>
    <item>
      <title>Arquitectura serverless para IoT</title>
      <dc:creator>Jhorman Villanueva</dc:creator>
      <pubDate>Thu, 27 Nov 2025 20:21:33 +0000</pubDate>
      <link>https://dev.to/jhorvi24/arquitectura-serverless-para-iot-248h</link>
      <guid>https://dev.to/jhorvi24/arquitectura-serverless-para-iot-248h</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" class="crayons-story__hidden-navigation-link"&gt;IoT sin servidores en&amp;nbsp;AWS:&amp;nbsp; Beneficios de los servicios serverless para&amp;nbsp;IoT&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jhorvi24" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3378338%2F908d5417-bc8d-4cb0-8671-1397d9eee413.jpg" alt="jhorvi24 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jhorvi24" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jhorman Villanueva
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jhorman Villanueva
                
              
              &lt;div id="story-author-preview-content-3061380" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jhorvi24" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3378338%2F908d5417-bc8d-4cb0-8671-1397d9eee413.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jhorman Villanueva&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 26 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" id="article-link-3061380"&gt;
          IoT sin servidores en&amp;nbsp;AWS:&amp;nbsp; Beneficios de los servicios serverless para&amp;nbsp;IoT
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/awsiot"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;awsiot&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/serverless"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;serverless&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/esp32"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;esp32&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>aws</category>
      <category>awsiot</category>
      <category>serverless</category>
      <category>esp32</category>
    </item>
    <item>
      <title>Arquitectura Serverless en AWS para proyectos IoT a bajo costo</title>
      <dc:creator>Jhorman Villanueva</dc:creator>
      <pubDate>Wed, 26 Nov 2025 15:42:49 +0000</pubDate>
      <link>https://dev.to/jhorvi24/arquitectura-serverless-en-aws-para-proyectos-iot-a-bajo-costo-mn6</link>
      <guid>https://dev.to/jhorvi24/arquitectura-serverless-en-aws-para-proyectos-iot-a-bajo-costo-mn6</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggapv6is8op5lnoyqcm4.png" height="400" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66" rel="noopener noreferrer" class="c-link"&gt;
            IoT sin servidores en&amp;nbsp;AWS:&amp;nbsp; Beneficios de los servicios serverless para&amp;nbsp;IoT - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            El IoT es una de las tecnologías de la cuarta revolución industrial que dota a los objetos de cierta...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" width="300" height="299"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>aws</category>
      <category>awsiot</category>
      <category>serverless</category>
      <category>esp32</category>
    </item>
    <item>
      <title>IoT sin servidores en AWS:  Beneficios de los servicios serverless para IoT</title>
      <dc:creator>Jhorman Villanueva</dc:creator>
      <pubDate>Wed, 26 Nov 2025 15:30:32 +0000</pubDate>
      <link>https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66</link>
      <guid>https://dev.to/jhorvi24/iot-sin-servidores-en-aws-beneficios-de-los-servicios-serverless-para-iot-m66</guid>
      <description>&lt;p&gt;El IoT es una de las tecnologías de la cuarta revolución industrial que dota a los objetos de cierta "inteligencia" para que perciban cambios en su entorno físico a través de sensores, procesen esos cambios usando un cerebro o en términos más técnicos un microcontrolador y dependiendo de ese procesamiento ejecuten una acción que podría ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Comunicar esos datos a un servidor en internet para mostrarlos a través de un tablero o Dashboard,&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Comunicar los datos a otro objeto,&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Activar un motor, encender un bombillo, etc.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A esos objetos con cierta inteligencia, David Rose, profesor del MIT media Lab, los denominó objetos encantados en su libro Enchanted Objects. Sin embargo, aquí le daremos la connotación de objetos inteligentes. Ese objeto inteligente debe contar con mínimo los siguientes componentes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F511jv0h5q8img87v8y50.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F511jv0h5q8img87v8y50.png" alt=" " width="397" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos a profundizar un poco sobre cada uno de esos componentes.&lt;/p&gt;

&lt;p&gt;Los sensores son como los sentidos del objeto inteligente los cuales permiten medir variables físicas como temperatura, humedad, presión, movimiento, pH, aceleración, luminosidad, gas, ruido, flujo, entre otros, y los cambios en esas variables físicas los relaciona con cambios de una señal eléctrica como resistencia, corriente o voltaje. Por ejemplo, hay sensores de temperatura como el sensor LM35 que produce un voltaje de salida linealmente proporcional a los cambios de temperatura en grados Celsius es decir que por cada grado que aumente la temperatura el sensor proporciona un aumento en voltaje de acuerdo con una relación que suministra el fabricante del sensor.&lt;/p&gt;

&lt;p&gt;Los sensores pueden proporcionar valores análogos que son continuos en el tiempo y pueden tomar un número infinito de valores dentro de un intervalo. Algunos sensores, en cambio, ya incorporan un conversor Análogo-Digital (ADC) y entregan valores digitales representados como ceros y unos. Para poder interpretar esos valores que arrojan los sensores, sean análogos o digitales, necesitamos un componente que procese esos datos y los convierta en magnitudes comprensibles por el ser humano, por ejemplo, temperatura expresada en grados Celsius o Fahrenheit, en lugar de voltios o bits. Ese componente es el cerebro del objeto inteligente o también denominado microcontrolador.&lt;/p&gt;

&lt;p&gt;El microcontrolador, a través de sus pines configurados como entrada, pueden recibir los datos medidos por los sensores ya en formato binario o digital que es el idioma que entienden los microcontroladores. En el microcontrolador se carga un programa que, dependiendo del tipo de microcontrolador, puede escribirse en lenguajes de programación como &lt;strong&gt;C&lt;/strong&gt;, &lt;strong&gt;MicroPython&lt;/strong&gt;, &lt;strong&gt;Arduino&lt;/strong&gt;, entre otros. Este programa implementa la lógica necesaria para procesar esos datos entregados por el sensor.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Por ejemplo, si el microcontrolador recibe datos de un sensor de temperatura, estos se procesan para obtener el valor en grados Celsius y mostrarlos a través de un display, si la temperatura supera un umbral definido, se puede ejecutar una acción como activar una alarma o, como se mencionó anteriormente, enviar estos datos a un servidor en internet.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Entre los microcontroladores más usados para proyectos de IoT se tiene el ESP32, las Raspberry pi pico y la familia de Arduino como el MKR GSM 1400, MKR WIFI1010, Portenta C33, entre otros.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwduh4vvj5epmxvm0719.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwduh4vvj5epmxvm0719.png" alt=" " width="572" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Algunos de los microcontroladores ya vienen con interfaces de comunicación inalámbricas integradas lo que les permite comunicar sus datos, a nivel de capa física (según el modelo OSI), a otros objetos utilizando tecnologías como Zigbee, Bluetooth, LoRaWAN o directamente a internet usando WI-FI. En esos esquemas donde los objetos inteligentes envían información a Internet, normalmente se utiliza el modelo cliente - servidor, donde los clientes son los objetos inteligentes y el servidor puede estar en una red local o en la nube de AWS. Es importante mencionar que, además de la conexión WIFI a nivel de capa física, a nivel de capa de aplicación la información se puede comunicar al servidor a través de protocolos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt;: modelo cliente - servidor, basado en TCP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MQTT&lt;/strong&gt;: modelo Publish-Suscribe (Publicación-Suscripción), basado en TCP, muy útil para aplicaciones de IoT por ser ligero, proporcionar menor consumo de energía y presentar un bajo consumo de ancho de banda.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CoAP&lt;/strong&gt;: modelo cliente - servidor, basado en UDP, útil para dispositivos muy restringidos en recursos.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por ejemplo, se puede implementar un servidor IoT en una instancia Amazon EC2 utilizando tecnologías de desarrollo backend como Node.js, PHP o en Python utilizando framework de desarrollo web como Flask, Django o FastAPI. En la siguiente figura se observa un esquema de esta implementación.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0ex7qwzkvhzsuj44oeb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0ex7qwzkvhzsuj44oeb.png" alt=" " width="671" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos a analizar lo que implica implementar un servidor IoT sobre una instancia de Amazon EC2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbbc0081wc46mvc3ure6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbbc0081wc46mvc3ure6.png" alt=" " width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cuando utilizamos Amazon EC2, nuestra responsabilidad es mayor, ya que debemos encargarnos de ciertas actividades, entre ellas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Aprovisionar una AMI con el sistema operativo determinado.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instalar y configurar los paquetes necesarios para la configuración y despliegue del servidor IoT. Verificar la actualización continuamente de estos paquetes.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configurar un Auto Scaling Group para el escalamiento automático según la carga.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configurar un balanceador de carga como un Application Load Balancer para garantizar alta Disponibilidad y distribución adecuada de tráfico.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y en temas de costos asociados a Amazon EC2 tendríamos que pensar en su modelo de precios que podría ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On demand&lt;/strong&gt;: se paga por la capacidad de cómputo por hora o segundo, es más costoso, pero no se tiene compromisos a largo plazo.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reserved instances&lt;/strong&gt;: permite un gran descuento, pero con compromiso de uso por uno o tres años.&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spot instance&lt;/strong&gt;: ofrece gran descuento (hasta un 90% con respecto con On-Demand), pero se debe tener en cuenta que la instancia puede ser interrumpida en cualquier momento si AWS necesidad la capacidad.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Arquitectura Serverless para IoT en&amp;nbsp;AWS
&lt;/h2&gt;

&lt;p&gt;El implementar una arquitectura serverless para IoT podría disminuirnos tareas operativas, tareas de administración y ahorrarnos costos ya que solamente vamos a pagar por lo que realmente vamos a usar. Además, podemos aprovecharnos de la alta escalabilidad y alta disponibilidad que ya viene integrada y asociada a los servicios administrados de AWS sin necesidad de gestionar servidores de forma directa. Aprovechando estos beneficios, a continuación, mostraré cómo implementar una arquitectura serverless para IoT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femn2lyd83k76w6ejjtb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femn2lyd83k76w6ejjtb1.png" alt=" " width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como dispositivo IoT vamos a usar el módulo ESP32, un microcontrolador de 32 bits que integra interfaces de comunicación inalámbrica como WI-FI y Bluetooth, y que cuenta con una SRAM de 520 KiB. En Colombia se puede conseguir a un precio de alrededor $30000 COP (aproximadamente 8 USD). Al ESP32 conectamos un sensor DHT11 que es un sensor digital que mide temperatura y humedad. Esos valores son procesados y organizados en el ESP32, y posteriormente enviados al servicio AWS IoT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void publishMessage()
{

  StaticJsonDocument&amp;lt;200&amp;gt; doc;

  // Añadir timestamp en formato ISO
  time_t now = time(nullptr);

  doc["timestamp"] = now;

  doc["humidity"] = h;
  doc["temperature"] = t;  //Organizar data en un json

  doc["device_id"] = "esp32_01";

  char jsonBuffer[512]; //crear un buffer
  serializeJson(doc, jsonBuffer); // Convertir en un string y almacenar en el buffer

  esp32.publish(monitoreo_topic, jsonBuffer);  //Public data al topic mqtt

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El servicio AWS IoT es un servicio serverless que nos provee una especie de Gateway totalmente administrado por AWS, a través del cual podemos conectar y comunicar dispositivos IoT de forma segura con la nube de AWS.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;La comunicación se puede realizar a través de MQTT  o mediante HTTPS. En ambos casos, el tráfico se cifra mediante certificados TLS y la autenticación de los dispositivos se realiza a través de certificados X.509.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;El servicio da la posibilidad de configurar reglas (Rules) que permite conectar los mensajes entrantes de los objetos inteligentes con otros servicios de AWS para su almacenamiento y posterior procesamiento. Por ejemplo, los datos se pueden almacenar en servicios como Amazon S3 o Amazon DynamoDB, procesarse a través AWS Lambda, generar notificaciones mediante Amazon SNS o analizar datos en tiempo real con Amazon Kinesis, entre otros.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Por ejemplo, yo configuré la siguiente regla para almacenar los datos en la base de datos de series de tiempo Timestream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhppwa87kx6kgzlik2pj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhppwa87kx6kgzlik2pj.png" alt=" " width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Antes de configurar la anterior Rule es importante crear la base de datos Timestream. Esta base de datos es una base de datos de series temporales totalmente administrada por AWS que nos ofrece dos motores: Amazon Timestream for LiveAnalytics y Amazon Timestream for Influxdb. Para la implementación de la arquitectura serverless trabajé con Amazon Timestream for LiveAnalytics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzkoluxh9mfkkyify4lz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzkoluxh9mfkkyify4lz.png" alt=" " width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hasta el momento el ESP32 envía datos a AWS IoT Core y de acuerdo con la rule configurada, estos datos se almacenan en Amazon Timestream. La pregunta ahora es:&lt;/p&gt;

&lt;p&gt;_ ¿Cómo podríamos visualizar esos datos almacenados en la base de datos a través de un dashboard aprovechando servicios serverless?_&lt;/p&gt;

&lt;p&gt;Imaginemos el siguiente escenario: a través del ESP32 estamos monitoreando las variables de un cultivo de tomate en invernadero (temperatura, humedad del suelo, luminosidad, etc) y el agricultor quiere ver esa información de forma gráfica desde cualquier dispositivo con acceso a internet y desde cualquier lugar.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;¿Qué servicios serverless de AWS necesitamos para lograrlo?&lt;/p&gt;

&lt;p&gt;Para cumplir con este objetivo vamos a utilizar los siguientes servicios: Amazon S3, Amazon API Gateway y AWS Lambda.&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt;: servicio de almacenamiento de objetos totalmente administrado por AWS que además podemos configurar como un hosting de una web estática. Es decir que, utilizando HTML, CSS y JS podemos construir el dashboard para IoT.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Como no soy desarrollador Frontend, utilicé Kiro, un IDE con IA, que me ayudó a construir el dashboard que se muestra en la siguiente imagen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx70jf3u98ineo3s95w2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx70jf3u98ineo3s95w2.png" alt=" " width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Necesitamos de un servicio que pueda leer la información de la base de datos y entregue los datos en un formato adecuado para mostrarlos en el Dashboard. Ese servicio es AWS Lambda.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: implementa la lógica para leer los datos de la base de datos y entregarlos al frontend de forma procesada. Es un servicio en el que configuramos funciones que se ejecutan en respuesta a un evento o trigger, por ejemplo, cuando se almacena un objeto en S3 puede generar un evento que dispare una función Lambda. Esas funciones se pueden escribir en lenguajes como Python, Javascript, Java, entre otros, sin necesidad de gestionar ni mantener servidores. En la siguiente función Lambda escribí el código para leer los datos de Timestream y entregarlos al frontend en formato JSON. A continuación, muestro un parte del código que corre en Lambda.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query = f"""
# obtener parámetros 
        query_params = event.get('queryStringParameters', {})
        device_id = query_params.get('device_id') if query_params else None
        limit = query_params.get('limit', '100') if query_params else '100'

        # Query para obtener todos los datos
        query = f"""
            SELECT 
                device_id,
                time as timestamp,
                measure_name,
                CAST("measure_value"::bigint AS double) AS value
            FROM sensor_values_db."values_sensors_esp32"
            WHERE measure_name IN ('humidity', 'temperature')
            {f"AND device_id = '{device_id}'" if device_id else ""}
            ORDER BY time DESC, device_id
            LIMIT {limit}
        """

        response = timestream_query.query(QueryString=query)

        # Procesar resultados y agrupar por timestamp y device_id
        readings = {}

        for row in response['Rows']:
            dev_id = row['Data'][0]['ScalarValue']
            ts_utc = row['Data'][1]['ScalarValue']
            measure_name = row['Data'][2]['ScalarValue']
            value = float(row['Data'][3]['ScalarValue'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como la intención es poder mostrar los datos a través de un dashboard, cada vez que un usuario o agricultor realice una petición (request) desde un navegador necesitamos de una API que invoque la función Lambda para leer los datos de la base de datos y traerlos en formato JSON para poder mostrarlos en la web. Esa API que conecta Amazon S3 (donde está la web estática para el dashboard) con AWS Lambda es Amazon API Gateway.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: servicio que nos permite crear, publicar, mantener, monitorear y asegurar APIs. Actúa como la puerta de entrada para que las aplicaciones accedan a datos, lógica de negocio o funcionalidad proveniente de los servicios Backend. En la arquitectura que estamos implementando, API Gateway funciona como un trigger de la función Lambda que definimos anteriormente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0sa90eftnyl648iti5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0sa90eftnyl648iti5.png" alt=" " width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora cada vez que un usuario realice una solicitud GET a la URL del sitio estático alojado en Amazon S3 (es decir cuando ingrese la URL en el navegador), el dashboard envía una solicitud a Amazon API Gateway, que a su vez dispara la función AWS Lambda para consultar la base de datos, traer la información en formato JSON y mostrarla en el dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6eg2r2fpwkl0uwpu7bw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6eg2r2fpwkl0uwpu7bw.png" alt=" " width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Con esta arquitectura totalmente serverless podemos ahorrar costos, reducir tareas operativas y de administración de infraestructura, aprovechamos la alta disponibilidad y escalabilidad y obtenemos una arquitectura flexible si necesitamos implementar proyectos basados en IoT.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Puedes encontrar el código completo en el siguiente repositorio de Github:&lt;br&gt;
&lt;a href="https://github.com/jhorvi24/aws-serverless-architecture-IoT.git" rel="noopener noreferrer"&gt;AWS_Serverless_Architecture&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awsiot</category>
      <category>serverless</category>
      <category>esp32</category>
    </item>
    <item>
      <title>aws</title>
      <dc:creator>Jhorman Villanueva</dc:creator>
      <pubDate>Tue, 29 Jul 2025 12:44:30 +0000</pubDate>
      <link>https://dev.to/jhorvi24/aws-8bp</link>
      <guid>https://dev.to/jhorvi24/aws-8bp</guid>
      <description></description>
    </item>
  </channel>
</rss>
