Introducción
En publicaciones anteriores de nuestra serie sobre microservicios, discutimos las características y los atributos necesarios para las aplicaciones de última generación. A medida que se construyen las infraestructuras de 5G y edge computing, las aplicaciones y los servicios confiables y de latencia ultrabaja se volverán cada vez más habituales, lo que acelerará aún más las altas expectativas de desempeño por parte de los usuarios. Además, con más personas que nunca trabajando, comprando y aprendiendo en línea, serán necesarias medidas estrictas de seguridad para cumplir los requisitos de conformidad y garantizar la privacidad del consumidor.
En este sentido, los proveedores serverless, como Azion, tienen la responsabilidad de suministrar una plataforma diseñada para maximizar el desempeño, la confiabilidad y la seguridad. Por este motivo, Azion ha elegido el lenguaje de programación Rust como la base de nuestra tecnología central, conocida como Azion Cells, que se utiliza en Edge Functions y en otros productos de Azion. Esta publicación ofrecerá una perspectiva más detallada de Rust, al examinar sus características y ventajas, así como también discutir sobre nuestra implementación de Rust en Azion Cells.
Origen
Rust es un lenguaje de programación de sistemas diseñado para ejecutar códigos seguros, confiables y con alto desempeño. Fue creado por el programador de Mozilla, Graydon Hoare, quien inició su desarrollo en 2006. En 2009, Mozilla respaldó a Rust como parte del proyecto Servo Parallel Browser, una iniciativa a largo plazo para reconstruir su navegador con tecnologías más actuales y seguras que sus implementaciones previas en C++.
El primer lanzamiento de una versión pre-alfa de Rust se realizó en 2012, mientras que su versión estable 1.0 fue lanzada en 2015. Desde entonces, este lenguaje ha ganado la atención y la popularidad de la comunidad tecnológica debido a su confiabilidad, seguridad y desempeño. Rust fue diseñado inicialmente como una alternativa a C y C++ para aplicaciones de alto desempeño, al tiempo que evita los problemas de memoria que a menudo surgen al programar en C y C++.
En una entrevista para InfoQ, Hoare explicó cuál fue su motivación para crear Rust:
“una gran cantidad de buenas ideas obvias, conocidas y amadas en otros lenguajes, no han sido implementadas en lenguajes de sistemas ampliamente usados, o en otros casos son implementadas en lenguajes con modelos de memoria muy deficientes (inseguros, hostiles a la concurrencia)… Quería revivir algunas de estas ideas y darles otra oportunidad, basado en la teoría de que algunas circunstancias han cambiado: Internet es muy concurrente y consciente de la seguridad, por lo que las compensaciones de diseño que estaban siempre a favor de C y C++ (por ejemplo) han cambiado”.
Criterios de diseño
El sitio web oficial de Rust lo describe como ”un lenguaje que permite a cualquiera construir un software confiable y eficiente”, al ofrecer su desempeño, confiabilidad y herramientas. Una publicación realizada en GitLab en 2020, enumera los principios clave del diseño de Rust:
- cumplimiento riguroso del préstamo seguro de datos;
- funciones, métodos y cierres para operar datos;
- listas de elementos (tuples), estructuras (structs) y enumeraciones (enums) para agregar datos;
- coincidencia de patrones para seleccionar y desestructurar datos;
- atributos que definen el comportamiento de los datos.
GitLab explica que estos atributos crean esencialmente “barreras de seguridad” al permitir que los desarrolladores que usan Rust puedan “crear códigos de rápido movimiento con pocas cosas que los ralenticen”
Características
En e blog de Stack Overflow se resaltan las características de Rust que mejoran tanto su velocidad como su seguridad. Este lenguaje está escrito de forma rigurosa y estática, lo que significa que las reglas de escritura son limitadas para garantizar exactitud y que sea comprobado durante la compilación y no en la ejecución (runtime). Su compilador tiene un verificador de préstamo (borrow checker) que permite la compatibilidad con versiones anteriores y “garantiza que las referencias no duren más que los datos a los que hacen referencia”.
Además, como indica Mozilla en un video de 2016, los mismos controles de seguridad que evitan errores en la memoria también detectan errores en los subprocesos múltiples (multi-thread), lo que permite un paralelismo y una concurrencia seguros. El paralelismo hace posible que el código sea ejecutado en múltiples núcleos al mismo tiempo, mientras que la concurrencia permite que dos o más tareas sean ejecutadas en períodos de tiempo superpuestos, lo que permite una ejecución más rápida y eficiente.
De igual forma, Rust ofrece abstracciones de costo cero y no usa un recolector de basura (garbage collector) para gestionar la memoria. Finalmente, a diferencia de C, Rust es seguro por defecto, lo que significa que, para escribir un código inseguro, debes optar por activarlo con una palabra clave.
- escritura fuerte y estática;
- comprobación de préstamos para compatibilidad de versiones anteriores;
- concurrencia y paralelismo seguros;
- abstracciones de costo cero;
- sin recolección de basura;
- opción para modo inseguro.
Ventajas de Rust
Aunque Rust no fue diseñado para ser un lenguaje funcional de programación, brinda el beneficio de escribir programas en un estilo funcional que mejore aún más su calidad y seguridad.
Confiabilidad y seguridad
Quizás la mayor ventaja de Rust es su habilidad para detectar y prevenir fallos de memoria. Como fue mencionado anteriormente, Rust es seguro por defecto, lo que significa que el lenguaje de programación gestiona la seguridad de inicio a fin.
Como explica The New Stack, “a un objeto se le asigna o reserva una cantidad apropiada de memoria. Cuando se accede a este objeto, es imposible acceder accidentalmente a una ubicación de memoria que está fuera de sus límites. Y cuando su trabajo es realizado, el sistema anulará la asignación del objeto automáticamente”. En otras palabras, la gestión de memoria no requiere que el desarrollador realice ningún cálculo y, como resultado, no ofrece la oportunidad para cometer e introducir errores.
Esta es una característica particularmente útil, ya que los errores de memoria pueden ser difíciles de detectar y pueden permanecer durante años sin ser detectados. Eliminar o, mejor aún, prevenir estos errores es crucial debido al alcance de sus consecuencias, que fueron descritas por Mozilla en esta publicación de 2019:
- falla (crash): termina un proceso de forma inesperada debido al acceso a memoria no válida;
- filtración de información: expone datos privados, tales como contraseñas;
- ejecución arbitraria de código (ACE, Arbitrary Code Execution): da de forma inadvertida a los atacantes la habilidad de ejecutar comandos arbitrarios en una máquina específica, o ejecución remota de código (RCE, Remote Code Execution) cuando ocurre en una red.
En otras palabras, Rust previene fallas de memoria que hacen que los programas sean menos confiables, además de hacerlos menos seguros. Como lo menciona Mozilla, “el mejor escenario para la mayoría de los errores de memoria, es cuando una aplicación falla sin causar daños; y este sigue sin ser el mejor de los casos. Sin embargo, el peor escenarios es que un atacante obtenga el control del programa a través de las vulnerabilidades (lo que podría ocasionar ataques futuros)”
Desempeño
A pesar de los posibles fallos de seguridad, se puede usar un código inseguro para obtener mejoras en el desempeño, de allí su uso predeterminado en C y C++. Y mientras Rust tiene un modo inseguro que puedes activar, el modo seguro también fue diseñado para obtener velocidad y eficiencia de recursos.
Para evitar errores introducidos al gestionar la memoria de forma manual, como es el caso en C y C++, muchos lenguajes usan un recolector de basura para hacer la gestión de memoria automáticamente. Sin embargo, este sistema de seguridad tiene un costo. Los programas deben ser pausados durante la recolección de basura, lo que da como resultado picos intermitentes de latencia. Para evitar esta pausa, Rust usa un modelo de su propiedad queimpone reglas que permiten al compilador gestionar la memoria sin disminuir el desempeño durante la ejecución de un código.
Además, Rust proporciona una forma para suministrar una ejecución rápida a través de una programación segura, concurrente y paralela. Durante algunas décadas, los avances en el desempeño de una CPU aumentaban constantemente, pero alcanzaron un límite donde ya no es posible una mayor optimización. Para acelerar el procesamiento de datos, algunas tareas deben ser ejecutadas en múltiples núcleos al mismo tiempo, lo que requiere una programación concurrente (superpuesta) o paralela (simultánea).
Sin embargo, este tipo de programación de subprocesos múltiples es complicado e introduce varios errores que pueden permanecer durante años sin ser detectados, lo que obliga a los desarrolladores a elegir entre velocidad y seguridad, una compensación que no es necesaria con Rust.
Comparación de Rust con otros lenguajes
Rust fue desarrollado explícitamente para resolver algunas de las frustraciones de los desarrolladores en relación a C y C++, y ha sido reconocido por mejorar estos problemas. Un artículo de opinión de Jon Evans en TechCrunch en 2015, que ha sido compartido ampliamente, postuló a Rust como una alternativa a C por “disminuir el bare metal y trabajar en velocidades” sin introducir “errores sutiles que pueden convertirse en enormes agujeros de seguridad”.
Rust también ha llamado la atención por resolver problemas de otros lenguajes de programación. En 2016, Sentry empleó Rust para hacer que el mapeo de fuentes fuese más rápido y más eficiente en recursos de lo que pudo lograr con Python debido a los encabezados de objetos y la recolección de basura de Python. De forma similar, Discord empezó a usar Rust en 2020 para reducir los picos de latencia que ocurren durante la recolección de basura forzada de Go y fue capaz de reducirla de forma significativa, así como también de optimizar la memoria y el CPU.
Como resultado de sus beneficios, Rust fue elegido como el lenguaje “más querido” en los últimos 5 años, de acuerdo a la encuesta anual para desarrolladores realizada por Stack Overflow. Además, es usado cada vez con mayor frecuencia para proyectos grandes, por lo que en junio de 2020 fue incluido en la lista con los 20 lenguajes de programación más populares de Tiobe. De igual forma, Microsoft lo ha adoptado para reducir errores y fallas de seguridad después de determinar que el 70 % de todos los parches de seguridad se deben a problemas relacionados con la memoria.
Implementación de Rust en Azion Cells
Mientras que AWS Lambda usa Node.js como el núcleo de su motor, escrito en C, C++ y JavaScript, el motor principal de Azion Cells está escrito en Rust. Esto permite una mayor confiabilidad y velocidad, así como un mejor desempeño con menos recursos, ya que es capaz de ejecutar JavaScript sin poner en marcha un proceso completo.
Además, Azion Cells usa V8 Isolates sobre un entorno multi-tenant que mantiene cada función aislada y segura, sin necesidad de usar contenedores que degradan aún más el desempeño con una latencia alta e impredecible debido a los arranques en frío (cold starts).
Se puede describir a Azion Cells como los bloques de construcción para Edge Functions, lo que permite una ejecución rápida, confiable y segura de funciones serverless en nuestra edge network. Edge Functions de Azion es ideal para aplicaciones modernas, en particular para la tecnología 5G, edge computing, Internet de las cosas (IoT) y otras tecnologías emergentes centradas en datos que elevan el nivel de desempeño, seguridad y confiabilidad.