Ahorrar costes de transferencia con instancias NAT gestionadas en AWS
Tags: aws, costes, nat, cdk
Published: 2024-05-02 08:20:11

Una de las principales preocupaciones cuando utilizamos la tecnología cloud son los costes y en ciertos casos los costes de transferencia de datos hacia o desde AWS pueden convertirse en una parte significativa de nuestra factura.

Este hecho se hace particularmente visible cuando utilizamos NAT Gateways y nuestras cargas de trabajo realizan muchas peticiones "hacia afuera", por ejemplo a otras APIs.

**NAT Gateway** es un servicio extraordinariamente potente y resiliente, pero dispone de un _pricing model_ que puede provocar fácilmente un aumento significativo de los costes de transferencia de datos, ya que **se aplican cargos por cada GB procesado por cada NAT Gateway**, sin importar si se trata de tráfico de entrada o de salida.

Sin embargo, usando instancias NAT (usando EC2), sólo incurrimos en costes de transferencia desde AWS hacia internet.

En este post hablaremos de una solución que implementa el uso de instancias NAT gestionadas en lugar de NAT Gateway de una forma eficiente y resiliente.

## Diseño de la solución

El principal problema de utilizar instancias NAT está en que _movemos_ la gestión y el mantenimiento de estas instancias de AWS hacia nosotros. Por eso es preferible utilizar servicios 100% gestionados por AWS como NAT Gateway y considerar usar instancias NAT sólo cuando es necesario.

La idea principal de esta solución es reducir a la mínima expresión la carga de trabajo requerida para realizar esa gestión y mantenimiento, automatizando la actualización regular de las instancias NAT y estableciendo mecanismos lo más automatizados posibles para realizar _Failover_ y _Fallback_.

De este modo, obtendremos una solución que una vez desplegada requerirá de muy poca atención y mantenimiento y que a su vez reducirá drásticamente nuestros costes de transferencia con respecto al uso de NAT Gateway.

Veamos un diagrama general de la solución:

![Diagrama de solución Instancias NAT autogestionadas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw0pxayib0dex1oiitpb.png)

El diseño de esta solución se basa parcialmente en la que [Ben Whaley](https://medium.com/@benwhaley) explica en el post _[How we reduced our AWS bill by seven figures](https://medium.com/life-at-chime/how-we-reduced-our-aws-bill-by-seven-figures-5144206399cb)_, aunque contiene algunos cambios significativos respecto a cómo se construyen las instancias NAT y a cómo son reemplazadas en caso de fallo o por mantenimiento.

Desde un punto de vista general, la solución tiene cuatro partes perfectamente diferenciadas:

- **Pipeline de creación de imágenes** - cómo las imágenes para las instancias NAT son creadas y distribuidas.
- **Recursos NAT en funcionamiento** - las instancias NAT en sí mismo y al menos una NAT Gateway en _standby_ como backup.
- _**Connectivity checker**_ - funciones Lambda diseñadas para comprobar que la conectividad dentro de las subredes privadas en nuestra VPC sigue funcionando; también se encargan de activar el proceso de _Failover_ en caso de fallo y de emitir métricas sobre la conectividad.
- _**Workflows**_ - varias máquinas de estado encargadas de manejar el ciclo de vida de la solución.

En el repositorio [Nat instances](https://github.com/neovasili/nat-instances), podréis encontrar una versión completa en CDK escrita en typescript de la solución aquí descrita.

Ahora veamos un poco más en detalle cada parte de la solución.

### Pipeline de creación de imágenes

Precisamos de un mecanismo confiable y seguro para producir nuevas imágenes para las instancias NAT actualizadas y sustituir las instancias en funcionamiento por unas que usen las imágenes nuevas.

AWS Image Builder es un servicio autogestionado justo para ése propósito. Con él crearemos el pipeline de creación de imágenes NAT.

Luego veremos cómo disparamos este pipeline desde la máquina de estados de mantenimiento a intervalos regulares.

El pipeline hace tres cosas diferentes:

- **Construir la imagen** - cogemos la imagen base y aplicamos los cambios para convertirla en una instancia NAT (ver [documentación de AWS](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_NAT_Instance.html)).
- **Testear la imagen** - con la imagen creada, se crea una instancia que la usa y se realizan varios tests para asegurar que hace lo que tiene que hacer.
- **Distribuir la imagen** - se crea una copia de la imagen creada final (y se aplican _Tags_) en cada región que designemos.

### Recursos NAT en funcionamiento

Aparte de nuestras instancias NAT creadas y en funcionamiento, necesitaremos tener también NAT Gateways disponibles que actuarán como backup en caso de que las instancias NAT fallen y también para utilizarlas como "puente" durante las tareas de mantenimiento.

El estado normal del sistema será que nuestras instancias NAT estarán emplazadas en las subredes públicas de nuestra VPC y las tablas de enrutamiento enviarán el tráfico saliente de las subredes privadas a las interfaces de red de las instancias, haciendo de este modo efectiva la traducción de direcciones de red.

Durante las tareas de mantenimiento, el sistema cambiará este enrutamiento para usar las NAT Gateways mientras que las instancias NAT son reemplazadas por una nueva versión de las mismas para finalmente, volver a cambiar el enrutamiento a las instancias NAT una vez que estén disponibles para su uso.

En caso de fallo de la conectividad de las instancias NAT, el sistema automáticamente cambia el enrutamiento a las NAT Gateways para que todo siga funcionando.

### Connectivity checker

¿Y cómo detecta el sistema que la conectividad de las instancias NAT ha dejado de funcionar? Pues para eso tenemos este componente que hemos denominado _Connectivity checker_.

En esencia, la solución emplaza una función Lambda en cada subnet privada, la cual se ejecuta de forma periódica y comprueba la conectividad "hacia afuera" de nuestro sistema haciendo peticiones a urls que le proveemos.

En cada ejecución, las funciones Lambda realizan las peticiones y si dichas peticiones fallan más de X veces (el umbral que definamos), el sistema considera que la conectividad está fallando y dispara el workflow de _Failover_, que como veremos en la siguiente sección, es el encargado de enrutar usando las NAT Gateways.

Un detalle interesante es que dado que las Lambdas están dentro de la red de nuestra VPC y que supuestamente la conectividad está fallando, necesitaremos tener VPC endpoints emplazados para poder "hablar" con el servicio _StepFunctions_, sino, el _Connectivity checker_ no podrá disparar el _Failover_.

### Workflows

Aquí es donde todo el sistema encierra la lógica que venimos describiendo en las secciones anteriores, esa orquestación y automatismo que nos ayudará a minimizar enormemente la gestión y el mantenimiento del sistema.

Para orquestar todos estos procesos, hemos elegido StepFunctions y sus StateMachines utilizando siempre que sea posible la integración de StepFunctions con los diferentes servicios de AWS. De este modo, existe mucho menos código propio que mantener, ya que dichas integraciones son gestionadas y mantenidas por AWS.

La solución define cuatro workflows en cuatro _StateMachines_ diferentes:

- **Failover**: es la encargada de reemplazar el enrutamiento para utilizar las NAT Gateways de backup.
- **Fallback**: realiza la operación contraria, reemplaza el enrutamiento para utilizar nuestras instancias NAT.
- **Reemplazo de instancias NAT**: dispara el _Failover_, y en paralelo elimina las instancias NAT actualmente en funcionamiento, mientras que provisiona nuevas instancias usando la última versión de las imágenes NAT creada.
- **Mantenimiento**: es la encargada de mantener actualizadas las instancias NAT, orquestando todos los pasos necesarios para hacerlo posible de forma segura y resiliente.

Veamos más en detalle esta última _StateMachine_.

#### State Machine de mantenimiento

La solución propone ejecutar este workflow de forma programada cada cierto tiempo (por ejemplo cada 14 días) para  asegurar que nuestras instancias NAT están debidamente actualizadas.

![Diagrama de State Machine de mantenimiento](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9algzo6111p2e60g9wz2.png)

La idea es bastante simple:

- Crear una nueva versión de las imágenes de nuestras instancias NAT usando el **Pipeline de creación de imágenes**.
- Efectuar el reemplazo de instancias NAT disparando el workflow para ello.
- Disparar el _Fallback_ para empezar a utilizar las nuevas instancias.

Con la StateMachine, nos aseguramos de que cada paso es ejecutado de forma automática y controlando qué sucede si algo falla, evitando así que nuestra conectividad deje de funcionar durante este proceso.

Además, gracias a StepFunctions, tenemos una forma muy visual de entender qué está sucediendo en cada momento y en caso de fallo dónde puede estar el problema.

## Conclusiones

Hemos visto cómo utilizando varios servicios gestionados de AWS podemos llegar a una solución para automatizar el uso, gestión y mantenimiento de instancias NAT en lugar de NAT Gateways, reduciendo considerablemente los costes de transferencia de datos en sistemas donde se realizan gran cantidad de peticiones externas desde nuestras subredes privadas.

En ese sentido, necesitaremos conocer nuestro _throughput_ máximo de red actual en el sistema para elegir un tamaño de las instancias NAT adecuado que permita preservar la mejora de costes pero que asegure el rendimiento del sistema.

StepFunctions y sus integraciones con otros servicios de AWS juegan un papel fundamental en esta solución, ya que nos permiten reducir casi a cero todo el trabajo de gestión y mantenimiento de código propio.

## Referencias

- [Repositorio nat-instances](https://github.com/neovasili/nat-instances)
- [How we reduced our AWS bill by seven figures](https://medium.com/life-at-chime/how-we-reduced-our-aws-bill-by-seven-figures-5144206399cb) por [Ben Whaley](https://medium.com/@benwhaley)
- [Instancias NAT - documentación de AWS](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_NAT_Instance.html)
- [Costes de NAT Gateways](https://aws.amazon.com/es/vpc/pricing/)

_Agradecimientos a [Gonzalo Camarero](https://twitter.com/gonzalocamarero) por su ayuda en la revisión del texto._