El estado de las librerías SSH de Python

En este post se comparará una nueva opción de librerías SSH de Python, ssh2-python, con Paramiko para demostrar su respectivo rendimiento con especial énfasis en la concurrencia y las peticiones no bloqueantes.

Al buscar una librería SSH de Python para utilizar en una aplicación, no existen muchas opciones. De hecho, la única librería de propósito general que ha estado, hasta ahora, disponible es Paramiko, que implementa la API SSH2 en código Python.

Para bien o para mal -es indudable que la librería ha ayudado a un gran número de personas con requerimientos similares, este autor incluido ya que ha sido históricamente la única opción- Paramiko ha sido el estándar de facto para las librerías SSH de Python hasta ahora.

Sin embargo, deja mucho que desear desde el punto de vista del rendimiento, la estabilidad y el consumo de recursos.

Por suerte, ahora hay otra opción en forma de ssh2-python que se basa en la biblioteca C libssh2.

Muchas aplicaciones de automatización como Ansible y Fabric hacen uso de Paramiko y probablemente estarían interesados en saber que ahora existen otras opciones. Esperemos que este post ayude a estas y otras aplicaciones a determinar qué biblioteca es más adecuada.

Comparación de rendimiento

ssh2-python es una nueva biblioteca SSH de Python basada en la biblioteca C libssh2. No tiene dependencias y proporciona ruedas binarias para Linux, OSX y Windows con libssh2 incluido.

paramiko está escrito en Python y hace uso de dependencias de extensiones nativas como la criptografía. Soporta Linux, OSX y Windows y sus dependencias nativas proporcionan ruedas binarias.

Configuración de la prueba

La prueba está utilizando la biblioteca estándar de Python threading para la concurrencia. En una entrada posterior del blog se examinará el rendimiento sin bloqueo en las bibliotecas a través de la biblioteca de co-rutinas gevent.

Aunque el threading no es un buen modelo para escalar la concurrencia de la E/S de la red, es el único modo de concurrencia que Paramiko soporta de forma nativa y es lo que utilizan aplicaciones como Ansible. Otros proyectos como parallel-ssh, del que también soy autor, utilizan el monkey patching de gevent para hacer que Paramiko sea concurrente de forma cooperativa. Eso también viene con desventajas significativas que el actualmente en desarrollo ssh2-python basado en un cliente nativo no-bloqueante pretende resolver. Vea el pull request de trabajo en progreso para más detalles sobre eso.

El script de prueba crea sesiones SSH en paralelo a un servidor SSH (OpenSSH) a través de un dispositivo de bucle de retorno (localhost), comenzando desde uno y aumentando en uno cada iteración hasta la finalización. Esto muestra cómo las dos bibliotecas escalan a medida que el número de sesiones paralelas aumenta.

El número máximo de hilos y por lo tanto de sesiones paralelas se establece en 50. Todas las pruebas se realizan en una CPU de cuatro núcleos físicos.

En todas las pruebas se utiliza la última versión disponible de cada librería, 2.2.1 y 0.5.3 para Paramiko y ssh2-python respectivamente. Para ssh2-python se utilizó un libssh2 embebido, última versión disponible 1.8.0. Todas las pruebas realizadas bajo Python 2.7.

Se comparan las librerías en seis operaciones SSH distintas, así como el tiempo total empleado desde el inicio hasta el final por sesión SSH.

Las seis operaciones comparadas son:

  • Inicialización de la sesión y autenticación con el agente SSH (auth)
  • Canal abierto (channel_open)
  • Canal ejecutado (execute)
  • Canal leído -. (channel_read)
  • Cierre de canal y obtención del estado de salida (close_and_exit_status)
  • Lectura de SFTP (sftp_read)
  • Una explicación rápida de lo que hacen estas operaciones:

    • Inicialización y autenticación de la sesión: realiza el handshake con el servidor SSH y se autentifica a través de un agente SSH del sistema.
    • Abrir canal – se requiere abrir un nuevo canal SSH en una sesión autenticada por cada comando remoto a ejecutar.
    • Ejecutar canal – ejecutar el comando shell cat en un archivo estático
    • Lectura de canal – recuperar la salida del comando.
    • Cierre de canal – se realiza cuando el comando ha terminado para recoger el estado de salida.
    • Lectura de SFTP – inicializa la sesión de SFTP, abre la manija del archivo remoto, lee los datos. Los datos leídos no se escriben en ninguna parte.
      • El archivo estático que se utiliza para el cat comando remoto es un 26KB archivo de licencia del repositorio ssh2-python.

        El archivo utilizado para la lectura del SFTP es un archivo tar comprimido de 11MB.

        Todas las duraciones están en milisegundos (ms).

        Resultados de la prueba

        Los gráficos muestran los valores medios por intervalos de treinta segundos.

        Gráfica de todas las operaciones

        Aquí se muestra cómo se comparan las dos bibliotecas en las cinco operaciones separadas más el tiempo total empleado.

        Haga clic en la imagen para verla más grande.

        comparación de ssh2 paramiko

        Como se puede ver arriba, las duraciones de Paramiko están dominadas por la lectura de SFTP, la autenticación y la apertura del canal, que siguen aumentando a medida que la concurrencia aumenta.

        En el caso de ssh2-python, el mayor tiempo se emplea en la lectura de SFTP, seguido de la autenticación y la apertura del canal.

        Ambas bibliotecas muestran picos intermitentes de duración que son esperables dado el número de hilos y las llamadas de bloqueo utilizadas.

        Operaciones individuales

        Tiempos de las operaciones sin incluir SFTP y el total para mostrar una visión más cercana del resto de los tiempos.

        Haga clic en la imagen para ver una versión más grande.

        comparación paramiko ssh2

        Rendimiento relativo

        Rendimiento relativo de las medias de las operaciones medianas de las dos bibliotecas para la duración de la prueba. Las duraciones de lectura total y SFTP son para los promedios.

        Por ejemplo, si una operación Paramiko fuera dos veces más rápida que la operación equivalente ssh2-python, su rendimiento relativo sería x0.5 de ssh2-python mientras que duraciones idénticas resultarían en x1 rendimiento relativo.

        Operación Paramiko ssh2 Paramiko/ssh2-python diferencia relativa
        auth 1.16 seg 675 ms x1.71
        channel open 1.248 seg 141 ms x8.85
        channel read 78 ms 29 ms x2.68
        close and exit status 4 ms 1 ms x4
        execute 24 ms 3 ms x8
        sftp_read 19.35 seg 1.13 s x17.12
        total 20,82 s 2.04 s x10.2

        Postfacio

        En total, ssh2-python se muestra considerablemente más rápido, particularmente en operaciones pesadas como SFTP, para las que la lectura es unas x17 veces más rápida de media en comparación con Paramiko. Otras operaciones que se benefician especialmente son la apertura de canales, x8 más rápida, y la ejecución, también x8. En total en el caso de prueba anterior ssh2-python se muestra como un orden de magnitud (x10) más rápido de media.

        También hay que tener en cuenta que el consumo de memoria no está graficado, pero es significativamente menor con ssh2-python como era de esperar al estar basado en una librería nativa.

        En cuanto a que las comparaciones entre una biblioteca SSH de código Python puro y una basada en una biblioteca C son «injustas» – la biblioteca de código Python «puro» también utiliza extensiones de código nativo para las operaciones de criptografía, lo que hace que el argumento sea discutible. Nótese cómo la operación con menor diferencia de rendimiento es la autenticación, que se maneja mayormente con extensiones de código nativo en Paramiko.

        Tampoco es necesario escribir una implementación de la API SSH de bajo nivel puramente en Python. De hecho, hay muchas razones para no hacerlo, como demuestran estos resultados.

        Apéndice

        El script de prueba utilizado puede encontrarse a continuación:

        • Script de prueba de rendimiento

        El script de prueba escribe datos de duración en una InfluxDB local a través de su servicio Graphite utilizando una plantilla measurement.field*.

        Los gráficos de arriba fueron generados por Grafana, como consultados desde InfluxDB por InfluxGraph usando la misma configuración de plantilla que InfluxDB.

        Para replicar los resultados, tenga cuidado de usar las versiones exactas de las bibliotecas utilizadas aquí, incluyendo libssh2. Las versiones más antiguas de libssh2 no son completamente seguras para los hilos y causarán caídas.

        Descargo de responsabilidad – Aunque soy el autor de ssh2-python, no tengo ningún interés en libssh2 ni en Paramiko. Mi interés en su respectivo rendimiento es para su uso en parallel-ssh.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *