2.6 ¿Cuándo actualizar? (I)
- 8 min de lectura
Existe una gran controversia por lo que refiere a las actualizaciones. Hay gente más temeraria partidaria de estar siempre a la última y en cuanto se publica una actualización son los primeros en aplicarla. Hay otra gente un poco más precavida que se espera un tiempo prudencial a la hora de instalar la última versión, por si aparecen errores, y actualizan cuando han sacado otra actualización menor que los corrige. Luego están los conservadores, que actualizan solo entre versiones estables, preferentemente con soporte a largo plazo, también conocidas como Long Term Support (LTS). Por último, están los dinosaurios, que no actualizan nunca y se quedan con las versiones que se usaron a la hora de poner el sistema en funcionamiento.
Ahora voy a contar mi opinión al respecto, según mi experiencia. Empezaré por decir que soy partidario de usar versiones estables LTS, siempre que sea posible, y hacer los saltos entre versiones de forma precavida y conservadora. Es decir, esperando un tiempo prudencial a que saquen al menos la primera release de bug fixing para la nueva versión estable. Suelen publicarlas al cabo de unos pocos días o semanas, después de que aparezcan los primeros bugs, aunque en ciertas ocasiones las liberan incluso el mismo día debido a fallos críticos encontrados.
Evidentemente, hasta ahora estaba hablando de hacer cambios de versión «grandes», pero dentro de las versiones LTS también se van publicando actualizaciones de seguridad, corrección de errores, mejoras, nuevas funcionalidades, etc. Sin embargo, las más importantes que hay que tener al día son las de seguridad, aunque las de resolución de errores también pueden resultar sumamente necesarias. Por lo tanto, instalando estas actualizaciones prioritarias tendremos nuestros sistemas más protegidos y libres de bugs.
Según mi experiencia, no me vale la pena usar siempre «la última versión», si realmente el cambio no me aporta nada y no se trata de una versión estable (preferentemente LTS). Indudablemente, queda descartado usar versiones no estables en sistemas en producción, como las consideradas experimentales o las nightly builds. En casos en que la versión actual tenga un bug que nos esté afectando y haya sido resuelto en una nueva versión, podemos vernos obligados a actualizar «forzosamente» antes de lo previsto. No obstante, como norma general, evitaremos usar versiones no finales en producción, como las pre-alfa, alfa, beta y release candidate (RC).
Hay que tener en cuenta que, trabajando en un producto con un sistema operativo (A), donde se use principalmente un lenguaje de programación (B), un framework (C), y un conjunto de librerías y otras dependencias (D), hay que tenerlos actualizados por este orden. No sirve de nada tener instaladas las últimas versiones de B, C y D, si el sistema operativo (A) no se ha actualizado y tiene agujeros de seguridad críticos que ya han sido corregidos en actualizaciones de seguridad publicadas y no instaladas. Además, si (A) no está actualizado, es muy probable que, aunque no tengamos actualizaciones pendientes de instalar para B, C y D, en realidad sí existan, pero no las podremos aplicar porque son incompatibles con nuestra versión antigua del sistema operativo. Algo parecido ocurrirá con el resto de componentes (B, C y D), de aquí que el orden sea tan importante.
Los cambios siempre conllevan oportunidades y riesgos. En este caso, las oportunidades son todo lo bueno que esperamos de las actualizaciones que vamos a instalar, por ejemplo, nuevas funcionalidades, mejoras de rendimiento, mayor estabilidad, etc. Los riesgos son los problemas que pueden aparecer debidos principalmente a incompatibilidades entre versiones y a errores introducidos en las nuevas versiones. Además, en ocasiones, las actualizaciones introducen cambios en el comportamiento por defecto, sin que seamos conscientes de ello, y para restablecer la normalidad debemos modificar la configuración para que vuelva a funcionar como antes. Sin embargo, no siempre se ofrece esta posibilidad y entonces tenemos que adaptarnos a los cambios o plantearnos alternativas.
Por estos motivos, no es nada recomendable actualizar directamente los sistemas en producción sin antes haberlos probado debidamente. Es cierto que existen herramientas que comprueban compatibilidades entre librerías, pero a veces las últimas versiones introducen bugs, mayormente si no se trata de versiones estables. Por lo tanto, hay que ir con mucho cuidado a la hora de actualizar librerías si el cambio no nos aporta nada y no hay actualizaciones pendientes de seguridad o de corrección de errores.
Para minimizar los riesgos de las actualizaciones, además de hacer las pruebas en local, necesitaremos al menos un entorno de preproducción que sea lo más parecido posible (idealmente igual) al sistema de producción. Entonces aplicaremos allí las actualizaciones, haremos todas las pruebas (automáticas y manuales) que necesitemos y una vez hechas todas las validaciones estaremos listos para actualizar los sistemas de producción. Hay que tener cuidado con los datos, ya que a veces, aunque las versiones de software sean iguales y las configuraciones sean correctas, los datos entre los entornos no lo son y esto puede provocar problemas inesperados a la hora de pasar a producción. Por lo tanto, si es posible, lo ideal es trabajar con sistemas análogos en todos los sentidos, incluso con los datos. Los problemas derivados de diferencias entre entornos son demasiado frecuentes y, de hecho, la frase «en local funciona» es muy típica, ¿a ti también te resulta familiar? Por supuesto, en el caso de actualizaciones comprometidas es muy conveniente tener un plan para ser capaces de dar marcha atrás en caso de necesidad y poder hacer un rollback de los cambios de forma segura.
Es muy importante fijar las versiones de las piezas de software que usemos (ya sea de forma exacta, o bien mediante rangos de versiones debidamente acotados) para evitar problemas con: paquetes de sistema, lenguajes de programación, frameworks, librerías… De esta forma, sabremos que esa combinación de versiones es compatible, la hemos probado y no tendremos tantas sorpresas inesperadas. Aun así, el hecho de fijar las versiones no es una garantía 100 % fiable de que no vayamos a tener problemas en el futuro con las dependencias de nuestros proyectos y nos podamos librar del infierno de las dependencias (dependency hell), ya que, por ejemplo, a veces son renombradas o incluso eliminadas. Además, también puedes encontrarte con problemas debido a que alguna de las dependencias fijadas tenga otras dependencias secundarias que no estén debidamente acotadas, o bien que hayan expirado las claves (GPG) o los certificados necesarios para su instalación, etc. Por motivos como estos, es muy importante actualizar las versiones de las dependencias de nuestros proyectos a medida que transcurre el tiempo y, por supuesto, no olvidarse de fijar las nuevas versiones instaladas.
Si trabajamos con herramientas de integración continua, entonces podremos tener automatizado el proceso de pruebas y despliegue con las versiones que hemos definido y no introduciremos errores humanos en el proceso. Hoy en día disponemos de muchísimas opciones y podemos elegir entre, por ejemplo, GitLab CI/CD, Jenkins, Travis CI, etc. En caso de no disponer de un entorno de integración continua, hay herramientas que permiten automatizar los procesos de despliegue, ejecutando los comandos necesarios de forma automatizada. Podríamos decir que están a medio camino entre el proceso manual y los entornos de integración continua. Una de estas herramientas es Fabric, que nos permite ejecutar scripts de forma remota vía SSH. El uso correcto de este tipo de herramientas contribuirá a evitar los «deploys a gritos», es decir, desplegar los cambios y esperar a ver quién grita primero o más fuerte.
Otra forma de automatización de las actualizaciones se produce cuando se requieren cambios en el formato de los datos y/o en la configuración. Por ejemplo, en un proyecto que lee datos en varios formatos desde múltiples fuentes, los procesa y luego los expone en forma de API REST, tuve que hacer una refactorización que afectaba a 8 de las 36 fuentes de datos. Esta actualización requería cambiar la forma en la que se configuraban cada una de estas fuentes. Tenía dos opciones: implementar la lógica para hacer que la propia actualización leyera las configuraciones actuales y las convirtiera al nuevo formato de forma automática, o bien dejar que lo hiciera una persona manualmente en el entorno de preproducción y posteriormente en el de producción. Hacerlo vía código suponía bastante trabajo extra, pero de esa forma me aseguraba de que no habría errores humanos, ya que se tenían que modificar varios campos por cada fuente. Evidentemente, lo automaticé para que la actualización fuera predecible, repetible y controlada. Un tiempo después agradecí muchísimo haberlo automatizado porque el despliegue estuvo parado durante meses debido a terceras personas y, como es lógico, ya no recordaba de memoria todos los pasos que se tendrían que hacer manualmente, con lo cual, me ahorré mucho tiempo, dolor y sufrimiento porque el trabajo duro ya estaba hecho.
Cuanto mayor es el intervalo de tiempo entre actualizaciones, mayor y más traumático es el cambio. Por lo tanto, como ya he dicho, mi recomendación es actualizar dentro de las versiones estables para tener los últimos parches de seguridad y de corrección de errores, y hacer las actualizaciones entre versiones estables de forma precavida, a medida que vayan siendo publicadas a lo largo del ciclo de vida del producto. De esta forma, los cambios no son tan bruscos ni peligrosos. Por ejemplo, si quisiéramos pasar de la versión 1 a la 5, porque en la versión 5 hay alguna funcionalidad que necesitamos, es muy probable que previamente tengamos que actualizar a la 2, 3 y 4, con el riesgo que conlleva. Es decir, vamos a tener que hacer de golpe todo el trabajo que supuestamente «nos habíamos ahorrado» y te puedo asegurar que eso no es nada agradable.
Asimismo, es conveniente ir actualizando las dependencias (sistema operativo, lenguaje de programación, framework y librerías) durante el desarrollo de los proyectos, si no ya estarán obsoletos antes de ponerlos en producción. Imaginemos el caso de un proyecto desarrollado durante varios años, si no se han actualizado las dependencias durante todo este tiempo, se va a poner en producción un sistema que tendrá dependencias antiguas y potencialmente vulnerables. Dicho de otra manera, el proyecto en el que hemos invertido tantos recursos ya estará desactualizado antes de empezar a usarlo. Por supuesto, hay que planificar debidamente estas actualizaciones, ya que pueden acarrear más trabajo, sobre todo en los casos de cambios de versiones mayores.
No hay que menospreciar nunca el trabajo que puede suponer una actualización, mayormente en el caso de aplicaciones o entornos con muchas dependencias, ya que puede convertirse en una tarea tremendamente ardua y costosa. Sin ir más lejos, el hecho de actualizar el sistema operativo puede causar todo tipo de problemas inesperados, que nos obligarán a actualizar en cascada las dependencias de nuestros proyectos. Por ejemplo, este tipo de actualizaciones pueden provocar que no podamos seguir usando las mismas versiones de algunas librerías que dependan de paquetes del sistema, aunque sus versiones estén debidamente fijadas. Además, es probable que el nuevo sistema solo soporte versiones más modernas del lenguaje de programación que estemos usando y esto cause infinidad de conflictos e incompatibilidades. Afortunadamente, la contenerización de las aplicaciones puede ayudarnos a desacoplarlas de los entornos donde se ejecutan. Aunque esto de por sí no significa que las aplicaciones estén siempre actualizadas ni que sea trivial mantenerlas al día, puede ponérnoslo más fácil si se hacen las cosas bien hechas.
Estas son solo algunas de las razones por las que los sistemas que ya tienen unos cuantos años, se encuentran en fase de mantenimiento (principalmente si es reactivo) y disponen de poco presupuesto tienden a ser congelados y no se actualizan. Sin embargo, con el paso del tiempo, se irán volviendo más y más obsoletos, con lo cual, el riesgo y el trabajo que conllevaría su actualización crece exponencialmente a medida que trascurren los años. Hasta que llega un momento en el que actualizarlos resulta prácticamente inviable y sale más a cuenta reemplazarlos por otros más modernos, iniciando nuevamente otro ciclo de obsolescencia progresiva y futuro reemplazo.
Continuará...
${ commentsData.total } comentario comentarios
Todavía no hay comentarios. ¡Sé el primero!
Inicia sesión para publicar, responder o reaccionar a los comentarios.
Inicia sesión para publicar, responder o reaccionar a los comentarios.
Respuesta para ${ replyToComment?.user.full_name }