UPD: la segunda parte del artículo está lista .Hola Habr! Me llamo Alexander Izmailov. En Badoo, lidero un equipo de ingenieros de lanzamiento. Sé que en muchas empresas puede enviar cambios de código a una persona especialmente capacitada, él los mira y los agrega a donde deberían (por ejemplo, esto es exactamente lo que sucede con el código Git). Y quiero hablar sobre cómo automatizamos este proceso con nosotros.
Mi historia constará de dos partes. En esta parte, hablaré sobre lo que queríamos lograr del nuevo sistema, cómo se veía en su primera versión y por qué al final tuvimos que rehacerlo. En la segunda parte, hablaremos sobre el proceso de rehacer el sistema y sobre las bonificaciones inesperadas que nos trajo.
Imagen:
fuenteÉrase una vez en nuestra empresa, todos podían hacer sus cambios directamente en la rama maestra y exponerlos con sus propias manos. Para hacer esto, escribimos una utilidad especial de MSCP que funcionó de manera bastante primitiva: copió los cambios de una máquina a otra y estableció los derechos necesarios.
A medida que pasó el tiempo, la empresa creció, y tuvimos que pensar en la automatización de los procesos, incluido el proceso de presentar pequeños cambios. Entonces se nos ocurrió la idea de crear un sistema de parches. En primer lugar, queríamos que permitiera a cualquier desarrollador enviar sus cambios a Git y distribuirlos en los servidores. Por nuestra parte, exigimos que otro desarrollador analice los cambios y que estén vinculados a la tarea desde nuestro sistema de seguimiento de errores (utilizamos Jira).
Patch collector 6000Debo decir que estos requisitos no atrajeron a todos los desarrolladores. Nos pareció que pasar un par de minutos creando una tarea no es una cosa, pero para nosotros esto significaría un uso más deliberado del sistema. Pero los desarrolladores comenzaron a resistirse, argumentando que presentar los cambios lleva varias veces menos tiempo que crear un nuevo boleto. Como resultado, todavía tenemos tareas "universales", a las que se adjuntan cientos de parches.
Además, el sistema fue concebido con el objetivo de solucionar problemas urgentes, y encontrar un revisor para un parche a las tres de la mañana puede ser difícil.
Que necesitamos
Sí, solo una luz en la ventana ... Nuestro problema podría dividirse en dos partes: necesitábamos alguna forma de aceptar los cambios en el repositorio y otra forma de presentar estos cambios.
Decidimos la primera pregunta con la suficiente rapidez: hicimos un formulario al que teníamos que adjuntar nuestros cambios e indicar los detalles (revisor y tarea).
En la segunda página, puede ver los cambios, rechazarlos o aceptarlos.

Después de la confirmación, los cambios cayeron en maestro.
Segunda pregunta: ¿cómo se pueden entregar estos cambios a los servidores rápidamente? Hoy en día, muchos utilizan la integración continua, y podría funcionar bien si nuestras construcciones y diseños "honestos" no tomaran tanto tiempo.
Asamblea justa
Nuestro montaje siempre ha sido bastante complicado. El principio general era el siguiente: en un directorio separado, distribuimos los archivos como estarían en los servidores de destino; luego guardamos este estado en una instantánea (instantánea del sistema de archivos) y la presentamos.
Ponemos el código PHP en el directorio, que tomamos del repositorio tal cual, agregamos los archivos generados (por ejemplo, plantillas y traducciones). Presentamos las estadísticas por separado. Este es un proceso bastante complicado, y puede dedicarle un artículo completo, pero como resultado, teníamos un mapa de versiones para generar enlaces de archivos para los usuarios que se fueron junto con el código principal.
A continuación, el estado del directorio debía guardarse en alguna parte. Para hacer esto, utilizamos un
dispositivo de bloque , que llamamos un bucle. Todo el directorio se copió a un dispositivo vacío, que luego se archivó y se entregó a servidores "principales" separados. De estos servidores tomamos archivos en el proceso de diseño. Cada archivo tenía un tamaño de 200 MB, y cuando se descomprimió, los bucles pesaron 1 GB. Nos llevó unos cinco minutos construir sin estática.
Diseño justo
Primero teníamos que entregar el archivo a los servidores de destino. Tenemos miles de ellos, por lo que la pregunta de entrega para nosotros siempre ha sido un gran problema: tenemos varias plataformas (centros de datos) y en los mil servidores "más gruesos" con un código. En un intento por lograr un mejor rendimiento (tiempo y recursos mínimos), probamos diferentes métodos: desde un SCP simple hasta torrents. Al final, decidimos usar UFTP. El método fue rápido (con buen tiempo, un minuto), pero desafortunadamente no estuvo libre de problemas. Periódicamente, algo se rompía y teníamos que recurrir a los administradores y a los networkers.
Después de que el archivo (de alguna manera) se encuentre en los servidores, debe desempaquetarse, lo que tampoco es gratuito. Este procedimiento parece especialmente costoso si recuerda que se realiza miles de veces, aunque en paralelo en diferentes máquinas.
Sin montaje
Por lo tanto, la publicación honesta de los cambios tomó mucho tiempo, y la velocidad de entrega fue muy importante para el sistema de parches, porque se suponía que lo usarían cuando algo ya no funcionara. Por lo tanto, volvimos a la idea de usar MSCP: rápido y fácil de implementar. Por lo tanto, después de que aparecieron los cambios en el asistente, en una página separada fue posible descomponer los archivos modificados a su vez.

Esta vivo
El sistema esta funcionando. A pesar de cierta insatisfacción con las pequeñas cosas, los desarrolladores podían hacer su trabajo, y para esto no necesitaban acceso al maestro o acceso a los servidores.
Pero, por supuesto, con este método de diseño, tuvimos problemas. Algunos eran predecibles, algunos incluso decidimos de alguna manera. La mayoría de ellos estaban relacionados con la edición de archivos en paralelo.
Un parche para múltiples archivos
Un ejemplo de un problema predecible. Se presentaron nuevos archivos a su vez. ¿Qué hacer si necesita cambiar varios archivos y los cambios están relacionados con ellos? Por ejemplo, quiero agregar un nuevo método en un archivo e inmediatamente usarlo en otros. Mientras no haya bucle invertido utilizando métodos (ver
recursión mutua ), es suficiente recordar el orden correcto de diseño del archivo.
Decisión honestaPara resolver el problema, necesitábamos reemplazar varios archivos atómicamente. En el caso de un solo archivo, la solución es conocida: debe usar el cambio de nombre de la operación de archivo. Supongamos que tenemos un archivo F y necesitamos reemplazar su contenido. Para hacer esto, cree un archivo TMP, escríbale la información necesaria y luego cambie el nombre de TMP F.
Vamos a complicar la tarea. Supongamos que tenemos un directorio D y necesitamos reemplazar su contenido. La operación de cambio de nombre no nos ayudará, ya que no puede reemplazar un directorio no vacío. Sin embargo, existe una solución alternativa: puede reemplazar previamente el directorio D con un llamado enlace simbólico (enlace simbólico). Entonces el contenido en sí mismo estará en otro lugar, por ejemplo, en el directorio D_1, y D será un enlace a D_1. En el momento en que se requiere reemplazo, el nuevo contenido se escribe en el directorio D_2, en el que se crea un nuevo enlace TMP. Ahora cambiar el nombre de TMP D funcionará porque esta operación se puede aplicar a los enlaces.
Esta solución parece apropiada: puede cambiar todo el directorio con el código, copiando archivos antiguos y escribiendo nuevos encima. El problema es que copiar todo el código es largo y costoso. Solo puede reemplazar el subdirectorio donde cambiaron los archivos, pero luego todos los subdirectorios con el código deben ser enlaces, porque no podemos reemplazar el directorio lleno con nada durante el proceso de diseño. Esta solución no solo parece muy complicada: debe recordar agregar algunas restricciones para que los dos procesos no puedan cambiar simultáneamente el mismo directorio o directorio y sus subdirectorios.
Como resultado, no pudimos encontrar una solución técnica, pero descubrimos cómo simplificar un poco la vida: realizamos el diseño de varios archivos con una sola acción en la interfaz. El desarrollador especificó el diseño de los archivos y el sistema los entregó.
Múltiples parches por archivo
Es más difícil si hay un archivo y hay varios desarrolladores que desean cambiarlo. Aplicamos el primer parche, pero no lo descomponemos. En este punto, llega el segundo parche y se le pide que se descomponga. Que hacer Aún más interesante, si se aplica el segundo parche, y en este momento se nos pide que descompongamos el primero.
Probablemente, necesitamos aclarar que siempre presentamos solo la última versión del asistente. De lo contrario, podrían surgir otros problemas. Por ejemplo, diseñando la versión anterior sobre la nueva.
No encontramos una solución realmente buena para este problema. Les mostramos a los desarrolladores la diferencia entre lo que presentan y lo que hay en las máquinas en un momento dado, pero esto no siempre funcionó. Por ejemplo, podría haber muchos cambios, y el desarrollador podría tener prisa o ser flojo (puede pasar cualquier cosa).
Muchos parches, y todos cambian los mismos archivos
Esta es la peor opción en la que ni siquiera quieres detenerte. Si los cambios de varios desarrolladores afectaron a varios de los mismos archivos, nuestro sistema de parches no podría ayudar particularmente: seguía dependiendo de la atención de los desarrolladores y su capacidad para comunicarse entre sí. Pero en teoría, es bastante posible obtener "peces" cuando, en cualquier orden de diseño, en algún punto del servidor habrá un código parcialmente roto.
Imagen:
fuenteProblemas de hierro
Otro problema surgió cuando, por alguna razón, uno de los servidores dejó de estar disponible. Teníamos un mecanismo para excluir dichos servidores del diseño, que funcionó bastante bien; dificultades aparecieron después de su regreso al deber. El hecho es que las versiones de las configuraciones y el código en los servidores en funcionamiento se verifican con nosotros (¡hay un departamento de monitoreo completo!), Y nos aseguramos de que todas las versiones estén actualizadas cuando el servidor vuelva a funcionar. Pero no teníamos ningún control de versiones para parches, simplemente copiamos nuevos archivos al código actual.
No encontramos una forma precisa de versionar los parches descompuestos, pero tratamos de resolver el problema con soluciones alternativas. Por ejemplo, rsync desde una máquina vecina al final del proceso de diseño. Pero de alguna manera no pudimos verificar de alguna manera.
Revisamos varias soluciones a este problema, por ejemplo, también queríamos aplicar parches en los servidores "principales" (es importante recordar que estamos implementando la versión empaquetada, es decir, necesitamos aplicar el parche y volver a empacar la versión), pero fue bastante difícil de implementar.
Una cucharada de miel
Pero, además de los problemas, hubo aspectos positivos.
En primer lugar, los desarrolladores descubrieron rápidamente que, además de arreglar las cosas, con la ayuda del sistema de parches, a veces puedes cargar nuevas funciones, por ejemplo, cuando las necesitas con urgencia. Como en cualquier empresa, tenemos fuerza mayor. Pero si antes teníamos que crear una construcción extraordinaria, en la que los probadores y los ingenieros de lanzamiento se distrajeron, ahora el desarrollador podría descomponer algunos cambios por su cuenta.
En segundo lugar, ya no se necesitaba una persona especial con derechos para arreglar algo. Cualquier desarrollador podría publicar sus ediciones. Pero esto no es todo: las compilaciones en general se han vuelto más fáciles, ahora los problemas se dividieron en críticos y aquellos que se pueden solucionar con parches. Esto hizo posible retroceder con menos frecuencia y tomar una decisión más rápida sobre si tuvimos éxito.
En pocas palabras, nos gustó el sistema y ganamos popularidad. Continuamos tratando de mejorarlo, pero con los problemas descritos tuvimos que vivir unos años más. Y cómo lo decidimos, cómo funciona el sistema ahora y cómo casi matamos las vacaciones de Año Nuevo durante el proceso de actualización, lo diré en la segunda parte del artículo.