Hoy estamos orgullosos de lanzar Pingora de código abierto, el marco Rust que hemos estado utilizando para desarrollar los servicios que impulsan una parte significativa del tráfico en Cloudflare. Pingora se ofrece bajo la licencia Apache versión 2.0.
Como mencionamos en nuestra publicación anterior del blog, Pingora es un marco multiproceso asíncrono de Rust que nos ayuda a construir servicios de proxy HTTP. Desde nuestra última publicación del blog, Pingora ha gestionado casi un cuatrillón de solicitudes de Internet en nuestra red global.
Publicamos Pingora en código abierto para ayudar a mejorar Internet y su seguridad más allá de nuestra propia infraestructura. Queremos proporcionar herramientas, ideas e inspiración a nuestros clientes, nuestros usuarios y a otras personas para que creen su propia infraestructura de Internet utilizando un marco seguro para la memoria. Disponer de un marco de este tipo es crucial dada la creciente concienciación sobre la importancia de la seguridad de la memoria en todo el sector y en el seno del gobierno de Estados Unidos. Con este objetivo común, colaboramos con Internet Security Research Group (ISRG) en el marco de su proyecto Prossimo para ayudar a hacer avanzar la adopción de Pingora en la infraestructura más crítica de Internet.
En nuestra publicación anterior del blog, comentamos por qué y cómo desarrollamos Pingora. En esta, hablaremos de por qué y cómo podrías utilizar Pingora.
Pingora ofrece elementos esenciales no solo para los proxies, sino también para los clientes y los servidores. Junto con estos componentes, también proporcionamos algunas bibliotecas de utilidades que implementan lógica común, como el recuento de eventos, la gestión de errores y el almacenamiento en caché.
Qué ofrece la solución
Pingora proporciona bibliotecas y API para desarrollar servicios sobre HTTP/1 y HTTP/2, TLS o simplemente TCP/UDP. Como proxy, admite HTTP/1 y HTTP/2 de extremo a extremo, gRPC y redireccionamiento de websockets mediante proxy. (La compatibilidad con HTTP/3 está en la hoja de ruta). También incluye estrategias personalizables de equilibrio de carga y de conmutación por error. En cuanto a la conformidad y la seguridad, es compatible con las bibliotecas OpenSSL y BoringSSL habitualmente utilizadas, que cumplen la normativa FIPS y ofrecen criptografía poscuántica.
Además de estas funciones, Pingora ofrece filtros y devoluciones de llamada para permitir a sus usuarios personalizar por completo cómo el servicio debe procesar, transformar y reenviar las solicitudes. Estas API resultan especialmente familiares para los usuarios de OpenResty y NGINX, ya que muchas se relacionan intuitivamente con las devoluciones de llamada "*_by_lua" de OpenResty.
Desde el punto de vista operativo, Pingora proporciona reinicios sin tiempo de inactividad para actualizarse sin interrumpir ni una sola solicitud entrante. Syslog, Prometheus, Sentry, OpenTelemetry y otras herramientas de observabilidad imprescindibles también se integran fácilmente con Pingora.
Quién puede beneficiarse de Pingora
Deberías considerar Pingora si:
La seguridad es tu máxima prioridad: Pingora es una alternativa más segura en cuanto a memoria para los servicios escritos en C/C++. Aunque algunos podrían aducir acerca de la seguridad de la memoria en los lenguajes de programación, nuestra experiencia práctica indica que somos mucho menos propensos a cometer errores de codificación que provoquen problemas de seguridad de la memoria. Además, puesto que dedicamos menos tiempo a resolver estos problemas, somos más productivos implementando nuevas funciones.
Tu servicio depende del rendimiento: Pingora es una solución rápida y eficiente. Como explicamos en nuestra publicación anterior del blog, hemos ahorrado muchos recursos de CPU y memoria gracias a la arquitectura multiproceso de Pingora. El ahorro de tiempo y de recursos podría resultar interesante en el caso de cargas de trabajo para las que el coste o la velocidad del sistema (o ambos) sean importantes.
Tu servicio requiere una gran personalización: las API que proporciona el marco de proxy de Pingora son muy programables. Para los usuarios que deseen crear una puerta de enlace o un equilibrador de carga personalizados y avanzados, Pingora ofrece formas eficaces pero sencillas de implementarlo. Proporcionamos ejemplos en la siguiente sección.
Desarrollemos un equilibrador de carga
Exploremos la API programable de Pingora desarrollando un equilibrador de carga sencillo. El equilibrador de carga seleccionará entre https://1.1.1.1/ y https://1.0.0.1/ para que sea un proceso ascendente round-robin.
En primer lugar, crearemos un proxy HTTP en blanco.
Cualquier objeto que implemente el rasgo ProxyHttp
(similar al concepto de una interfaz en C++ o Java) es un proxy HTTP. El único método necesario es upstream_peer()
, al que se llama para cada solicitud. Esta función debe devolver un HttpPeer
que contenga la dirección IP de origen a la que conectarse y cómo conectarse.
pub struct LB();
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
todo!()
}
}
A continuación, implementemos la selección round robin. El marco de Pingora ya proporciona al LoadBalancer
algoritmos de selección comunes, como round robin y hash, así que vamos a utilizarlo. Si el caso de uso requiere una lógica de selección de servidor más sofisticada o personalizada, los usuarios pueden simplemente implementarla ellos mismos en esta función.
Como nos estamos conectando a un servidor HTTPS, también es necesario configurar la SNI. Si es necesario, los certificados, los tiempos de espera y otras opciones de conexión también se pueden configurar aquí en el objeto HttpPeer.
pub struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
let upstream = self.0
.select(b"", 256) // hash doesn't matter for round robin
.unwrap();
// Set SNI to one.one.one.one
let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
Ok(peer)
}
}
Por último, pongamos el servicio en práctica. En este ejemplo, codificamos las direcciones IP del servidor de origen. En las cargas de trabajo reales, las direcciones IP del servidor de origen también se pueden descubrir dinámicamente al llamar a upstream_peer()
o en segundo plano. Una vez creado el servicio, simplemente le indicamos al servicio LB que esté a la escucha de 127.0.0.1:6188. Al final hemos creamos un servidor de Pingora, y el servidor será el proceso que ejecute el servicio de equilibrio de carga.
Probémoslo:
fn main() {
let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();
let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
lb.add_tcp("127.0.0.1:6188");
let mut my_server = Server::new(None).unwrap();
my_server.add_service(lb);
my_server.run_forever();
}
Podemos ver que el proxy funciona, pero el servidor de origen nos rechaza con un error 403. Esto se debe a que nuestro servicio simplemente redirecciona el encabezado del host, 127.0.0.1:6188, establecido por curl, lo que no le gusta al servidor de origen. ¿Cómo hacemos que el proxy corrija eso? Para ello, podemos simplemente añadir otro filtro llamado upstream_request_filter
. Este filtro se ejecuta en cada solicitud después de que se conecte el servidor de origen y antes de que se envíe cualquier solicitud HTTP. Podemos añadir, eliminar o cambiar los encabezados de solicitud http en este filtro.
curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
Intentémoslo de nuevo:
async fn upstream_request_filter(…, upstream_request: &mut RequestHeader, …) -> Result<()> {
upstream_request.insert_header("Host", "one.one.one.one")
}
¡Esta vez funciona! Encontrarás el ejemplo completo aquí.
curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK
A continuación se muestra un diagrama muy sencillo de cómo fluye esta solicitud a través de la devolución de llamada y el filtro que utilizamos en este ejemplo. El marco de proxy de Pingora actualmente proporciona más filtros y devoluciones de llamada en distintas etapas de una solicitud para permitir a los usuarios modificar, rechazar, enrutar y/o registrar la solicitud (y la respuesta).
En segundo plano, el marco de proxy de Pingora se encarga de la agrupación de conexiones, los protocolos de enlace TLS, la lectura, la escritura, el análisis de las solicitudes y otras tareas habituales del proxy para que los usuarios puedan centrarse en la lógica que les interesa.
Código abierto, presente y futuro
Pingora es una biblioteca y un conjunto de herramientas, no un binario ejecutable. En otras palabras, Pingora es el motor que impulsa un coche, no el coche en sí. Aunque Pingora está lista para producción para su uso en el sector, entendemos que mucha gente quiere un servicio web listo para usar con todo incluido, con opciones de configuración que apenas requieran código o que no lo requieran. Desarrollar esa aplicación sobre Pingora será el objetivo de nuestra colaboración con ISRG para ampliar el alcance de Pingora. No te pierdas las próximas novedades sobre ese proyecto.
Otras advertencias a tener en cuenta:
Hoy en día, la estabilidad de las API no está garantizada. Aunque intentaremos minimizar la frecuencia con la que realicemos cambios importantes, nos reservamos el derecho de añadir, eliminar o cambiar componentes como los filtros de solicitudes y respuestas a medida que evolucione la biblioteca, especialmente durante este periodo previo a la versión 1.0.
La compatibilidad con sistemas operativos no basados en Unix no está actualmente en la hoja de ruta. No tenemos planes inmediatos para ofrecer compatibilidad con estos sistemas, aunque esto podría cambiar en el futuro.
Cómo contribuir
No dudes en plantear informes de errores, problemas de la documentación o solicitudes de funciones en nuestro rastreador de problemas de GitHub. Antes de abrir una solicitud de extracción, te recomendamos encarecidamente que eches un vistazo a nuestra guía de contribuciones.
Conclusión
En esta publicación, hemos anunciado la publicación del código abierto de nuestro marco Pingora. Demostramos que las entidades y la infraestructura de Internet pueden beneficiarse de la seguridad, el rendimiento y la personalización de Pingora. También demostramos lo fácil que es utilizar Pingora y lo personalizable que es.
Tanto si desarrollas servicios web de producción como si experimentas con tecnologías de red, esperamos que Pindora te resulte útil. Ha sido un largo recorrido, pero compartir este proyecto con la comunidad de código abierto ha sido nuestro objetivo desde el principio. Nos gustaría dar las gracias a la comunidad de Rust, ya que Pingora se ha desarrollado con muchos paquetes Rust de código abierto que nos han sido de gran utilidad. Migrar a una red de Internet segura para la memoria puede parecer un camino imposible, pero esperamos que te unas a nosotros.